<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://docs.moodle.org/dev/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Pferre22</id>
	<title>MoodleDocs - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://docs.moodle.org/dev/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Pferre22"/>
	<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/Special:Contributions/Pferre22"/>
	<updated>2026-08-04T03:50:55Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Customization&amp;diff=62163</id>
		<title>Moodle Mobile 2 (Ionic 1) Customization</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Customization&amp;diff=62163"/>
		<updated>2022-04-29T07:14:36Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 2 (Ionic 1)}}&lt;br /&gt;
IMPORTANT: This documentation will be obsolete in May 2018, please [https://moodle.org/mod/forum/discuss.php?d=360412 read this announcement].&lt;br /&gt;
== Overview ==&lt;br /&gt;
Moodle Mobile can be customized in different ways:&lt;br /&gt;
* Changing the app appearance for your site.&lt;br /&gt;
* Creating a set of remote add-ons in order to add additional functionalities to the app.&lt;br /&gt;
* Creating a custom application (with your custom name and icon) based on the Moodle Mobile code.&lt;br /&gt;
* Configuring how the app should work via Moodle site settings.&lt;br /&gt;
With the first two options you are not required to compile or build your custom application, with the third one you are required to compile and publish your custom app in the stores.&lt;br /&gt;
== Changing the app appearance for your site ==&lt;br /&gt;
The Mobile app can retrieve your custom styles from your Moodle site. Since is an HTML5 app, you can apply safely CSS3 styles.&lt;br /&gt;
&lt;br /&gt;
In your Moodle installation go to Plugins / Web services / Mobile and enter in the mobilecssurl field a valid URL pointing to a CSS file containing your custom styles (theme).&lt;br /&gt;
&lt;br /&gt;
The CSS should be placed inside your Moodle installation (in your custom theme or inside a local plugin)&lt;br /&gt;
&lt;br /&gt;
Once the user is logged in the app, there is a periodical process that retrieves your remote CSS files for applying your custom styles into the app.&lt;br /&gt;
&lt;br /&gt;
Notice that on the first time a user opens the app, he will see the default &amp;quot;orange&amp;quot; style. Your custom styles will be applied once the user has added a site in the app.&lt;br /&gt;
&lt;br /&gt;
Notice also that styles comes from a site, there is no way for changing the initial Add site / Manage account pages styles.&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Themes]] for additional information.&lt;br /&gt;
== Remote add-ons  ==&lt;br /&gt;
Remote add-ons are available since Moodle 3.1 and Moodle Mobile 3.1.&lt;br /&gt;
&lt;br /&gt;
A Moodle Mobile remote add-on is the mobile app version of a Moodle plugin that will be loaded when a user accesses the site on the app.&lt;br /&gt;
&lt;br /&gt;
Moodle Mobile remote add-ons are stored within a Moodle plugin and are downloaded and lazy-loaded by the Mobile app in every site that uses them. &lt;br /&gt;
&lt;br /&gt;
Remote add-ons are the best choice if you just want to support a Moodle plugin in the Mobile app without having to create and maintain a custom version.&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Remote add-ons]] for complete information.&lt;br /&gt;
== Creating a custom application ==&lt;br /&gt;
See [[Moodle Mobile custom apps]] or [https://moodle.com/mobileapp/ The Branded Moodle Mobile App service] by Moodle HQ&lt;br /&gt;
=== Building ===&lt;br /&gt;
There are two options for building the app, aka compile it to different devices:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Phonegap Build&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Phonegap Build is an online service that lets you build the app automatically for different platforms. The main advantage is that it simplifies a lot the process of building.&lt;br /&gt;
&lt;br /&gt;
You just put your www/ folder in a github account and Phonegaps automatically builds the app for different platforms pulling your github code.&lt;br /&gt;
&lt;br /&gt;
Notice that the free version is for open repositories, but you can use the non-free version for private repositories.&lt;br /&gt;
&lt;br /&gt;
In order to build your app using Phonegap Build you need to place a special config.xml file into the www/ folder, see the config.xml file for Moodle Mobile 2 (Please, note that this file is different from the one created by Ionic in the app root folder). Please follow the tutorial mentioned in [[Moodle Mobile custom apps]] to view all the steps required for building the app using Phonegap Build service.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ionic CLI&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You can also build the app using the Ionic CLI tools, see: http://ionicframework.com/docs/cli/run.html&lt;br /&gt;
== Configuring how the app should work via Moodle site settings ==&lt;br /&gt;
The app can be configured via Moodle site settingss. You can disable features, rename strings, add new elements to the main menu or change how log out works in the app. This requires Moodle 3.3 or the [[https://docs.moodle.org/en/Moodle_Mobile_additional_features Moodle Mobile additional features plugin]].&lt;br /&gt;
&lt;br /&gt;
Please refer to the Configuring the app from your site section in this page: https://docs.moodle.org/en/Moodle_Mobile_guide_for_admins&lt;br /&gt;
== Testing and developing ==&lt;br /&gt;
=== Chromium or Google Chrome browser ===&lt;br /&gt;
See [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
== Moodle Desktop ==&lt;br /&gt;
Refer to [[Moodle Desktop]]&lt;br /&gt;
== See also ==&lt;br /&gt;
[https://moodle.com/mobileapp/ Branded Moodle Mobile app service by Moodle HQ]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Developing_a_plugin_tutorial&amp;diff=62162</id>
		<title>Moodle Mobile 2 (Ionic 1) Developing a plugin tutorial</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Developing_a_plugin_tutorial&amp;diff=62162"/>
		<updated>2022-04-29T07:14:32Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 2 (Ionic 1)}}&lt;br /&gt;
&lt;br /&gt;
IMPORTANT: This documentation will be obsolete in May 2018, please [https://moodle.org/mod/forum/discuss.php?d=360412 read this announcement].&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This tutorial describes all the steps needed for developing an add-on for the Moodle Mobile app.&lt;br /&gt;
&lt;br /&gt;
In this tutorial we make the following assumptions:&lt;br /&gt;
&lt;br /&gt;
* You have a medium/good skills/understand of AngularJS/Ionic, PHP and Moodle architecture&lt;br /&gt;
* You know what a Web Service is and how it works in Moodle&lt;br /&gt;
* The plugin you are going to develop requires a Web Service in your Moodle installation to work&lt;br /&gt;
&lt;br /&gt;
There is a second part of this tutorial where we extend the add-on features [[Moodle Mobile Developing a plugin tutorial part 2]]&lt;br /&gt;
&lt;br /&gt;
== Add-on spec ==&lt;br /&gt;
&lt;br /&gt;
We want to develop a plugin that will display a new button in the user profile for adding new notes to the user. This plugin is suitable for course managers of teachers.&lt;br /&gt;
&lt;br /&gt;
A couple of screenshots with the desired result:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| [[File:MoodleMobile2Tutorial01.png|thumb|Initial  view]]&lt;br /&gt;
| [[File:MoodleMobile2Tutorial02.png|thumb|Course contents view]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Steps ==&lt;br /&gt;
&lt;br /&gt;
=== Set up your development environment ===&lt;br /&gt;
&lt;br /&gt;
Please, read [[Setting up your development environment for Moodle Mobile 2]]&lt;br /&gt;
&lt;br /&gt;
=== Set up your Moodle installation ===&lt;br /&gt;
&lt;br /&gt;
Enable debugging, disable caches, etc... the typical settings for developing.&lt;br /&gt;
&lt;br /&gt;
Enable the Mobile Service via Admin / Plugins / Web Services / Mobile&lt;br /&gt;
&lt;br /&gt;
=== Develop the Moodle Web Services you are going to need ===&lt;br /&gt;
&lt;br /&gt;
In this case we&#039;ll use an existing web service (core_notes_create_notes) that is already available in the Moodle Mobile app service, if you need custom functions you will need to develop a local plugin including there your new Web Services (see https://moodle.org/plugins/view/local_wstemplate) and create a custom service.&lt;br /&gt;
&lt;br /&gt;
=== Develop the Moodle Mobile add-on ===&lt;br /&gt;
&lt;br /&gt;
The full source code can be found here: https://github.com/moodlehq/moodlemobile2/tree/master/www/addons/notes&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;www/addons/notes/main.js&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
angular.module(&#039;mm.addons.notes&#039;, [])&lt;br /&gt;
&lt;br /&gt;
.run(function($mmUserDelegate, $mmaNotesHandlers) {&lt;br /&gt;
&lt;br /&gt;
    $mmUserDelegate.registerPlugin(&#039;mmaNotes:addNote&#039;, $mmaNotesHandlers.addNote);&lt;br /&gt;
&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we declare the plugin, and register the Plugin (this means that the plugin will be displayed in the User profile.&lt;br /&gt;
&lt;br /&gt;
We will use the $mmaNotesHandlers service for holding all the code related to the plugin availability and main functions&lt;br /&gt;
&lt;br /&gt;
There are more delegates for registering your plugin, see:&lt;br /&gt;
* www/addons/mod_forum/main.js (Module delegate, and module actions delegate)&lt;br /&gt;
* www/addons/messages/main.js (Main side menu delegate, and multiple delegates for the user profile)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;www/addons/notes/services/handlers.js&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
self.isEnabled = function() {&lt;br /&gt;
    return $mmaNotes.isPluginEnabled();&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
self.isEnabledForUser = function(user, courseId) {&lt;br /&gt;
   // Active course required.&lt;br /&gt;
   return courseId &amp;amp;&amp;amp; user.id != $mmSite.getUserId();&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These are mandatory functions that will check our plugin availability, the first one will rely in the $mmaNotes service (isPluginEnabled function), the second ones checks that the user profile is being viewed inside a course (because the WS requires it) and that the current user is not the required user (so you don&#039;t see the buttons in your own profile).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        self.getController = function(user, courseid) {&lt;br /&gt;
&lt;br /&gt;
            return function($scope) {&lt;br /&gt;
&lt;br /&gt;
                // Button title.&lt;br /&gt;
                $scope.title = &#039;mma.notes.addnewnote&#039;;&lt;br /&gt;
&lt;br /&gt;
                $ionicModal.fromTemplateUrl(&#039;addons/notes/templates/add.html&#039;, {&lt;br /&gt;
                    scope: $scope,&lt;br /&gt;
                    animation: &#039;slide-in-up&#039;&lt;br /&gt;
                }).then(function(m) {&lt;br /&gt;
                    $scope.modal = m;&lt;br /&gt;
                });&lt;br /&gt;
&lt;br /&gt;
                $scope.closeModal = function(){&lt;br /&gt;
                    $scope.modal.hide();&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
                $scope.addNote = function(){&lt;br /&gt;
                    // Freeze the add note button.&lt;br /&gt;
                    $scope.processing = true;&lt;br /&gt;
&lt;br /&gt;
                    $mmaNotes.addNote(user.id, courseid, $scope.note.publishstate, $scope.note.text).then(function() {&lt;br /&gt;
                        $translate(&#039;mma.notes.eventnotecreated&#039;).then(function(str) {&lt;br /&gt;
                            $ionicLoading.show({&lt;br /&gt;
                                template: str,&lt;br /&gt;
                                duration: 2000&lt;br /&gt;
                            });&lt;br /&gt;
                        });&lt;br /&gt;
                    }, function(error) {&lt;br /&gt;
                        $mmUtil.showErrorModal(error);&lt;br /&gt;
                    }).finally(function() {&lt;br /&gt;
                        $scope.closeModal();&lt;br /&gt;
                    });&lt;br /&gt;
                };&lt;br /&gt;
&lt;br /&gt;
                $scope.action = function($event) {&lt;br /&gt;
                    $event.preventDefault();&lt;br /&gt;
                    $event.stopPropagation();&lt;br /&gt;
&lt;br /&gt;
                    $scope.note = {&lt;br /&gt;
                        publishstate: &#039;personal&#039;,&lt;br /&gt;
                        text: &#039;&#039;&lt;br /&gt;
                    };&lt;br /&gt;
                    $scope.processing = false;&lt;br /&gt;
&lt;br /&gt;
                    $scope.modal.show();&lt;br /&gt;
                };&lt;br /&gt;
            };&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These is the main function code, it does the following:&lt;br /&gt;
* $scope.title = &#039;mma.notes.addnewnote&#039;; This sets the button title, note that we reference to the addnewnote string that is in the www/addons/notes/lang/en.json file (we need to prefix the string id to identify the plugin)&lt;br /&gt;
* $ionicModal.fromTemplateUrl - Creates the modal we are going to use for displaying the form, the HTML code of the form is in www/addons/notes/templates/add.html&lt;br /&gt;
* $scope.closeModal - Helper function for closing the modal inside the modal via a button&lt;br /&gt;
* $scope.addNote - This function will be called when the user clicks the &amp;quot;Add a note&amp;quot; button in the modal, it will use the $mmaNotes service that handles all the logic WS logic for creating the note&lt;br /&gt;
* $scope.action - This function will be called when the user clicks the &amp;quot;Add a note&amp;quot; button in the user profile view, it will open the modal and set the default values&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;www/addons/notes/services/notes.js&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
angular.module(&#039;mm.addons.notes&#039;)&lt;br /&gt;
&lt;br /&gt;
.factory(&#039;$mmaNotes&#039;, function($mmSite, $log, $q) {&lt;br /&gt;
    $log = $log.getInstance(&#039;$mmaNotes&#039;);&lt;br /&gt;
&lt;br /&gt;
    var self = {};&lt;br /&gt;
&lt;br /&gt;
    self.addNote = function(userId, courseId, publishState, noteText) {&lt;br /&gt;
        var data = {&lt;br /&gt;
            &amp;quot;notes[0][userid]&amp;quot; : userId,&lt;br /&gt;
            &amp;quot;notes[0][publishstate]&amp;quot;: publishState,&lt;br /&gt;
            &amp;quot;notes[0][courseid]&amp;quot;: courseId,&lt;br /&gt;
            &amp;quot;notes[0][text]&amp;quot;: noteText,&lt;br /&gt;
            &amp;quot;notes[0][format]&amp;quot;: 1&lt;br /&gt;
        };&lt;br /&gt;
        return $mmSite.write(&#039;core_notes_create_notes&#039;, data);&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    self.isPluginEnabled = function() {&lt;br /&gt;
        var infos;&lt;br /&gt;
&lt;br /&gt;
        if (!$mmSite.isLoggedIn()) {&lt;br /&gt;
            return false;&lt;br /&gt;
        } else if (!$mmSite.canUseAdvancedFeature(&#039;enablenotes&#039;)) {&lt;br /&gt;
            return false;&lt;br /&gt;
        } else if (!$mmSite.wsAvailable(&#039;core_notes_create_notes&#039;)) {&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    return self;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This file contains the notes service:&lt;br /&gt;
* isPluginEnabled - Checks if the notes functionality is available by checking Moodle settings and the required WS availability&lt;br /&gt;
* addNote - Will call the remote WS for adding the note via the $mmSite.write helper method (the $mmService has also a read method for retrieving data)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;/www/addons/notes/templates/add.html&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-modal-view class=&amp;quot;product edit create&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;form name=&amp;quot;itemEdit&amp;quot; ng-submit=&amp;quot;addNote()&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-header-bar class=&amp;quot;bar-header&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;h1 class=&amp;quot;title&amp;quot;&amp;gt;{{ &#039;mma.notes.addnewnote&#039; | translate }}&amp;lt;/h1&amp;gt;&lt;br /&gt;
            &amp;lt;a class=&amp;quot;button&amp;quot; ng-click=&amp;quot;closeModal()&amp;quot;&amp;gt;{{ &#039;mm.core.cancel&#039; | translate }}&amp;lt;/a&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-header-bar&amp;gt;&lt;br /&gt;
        &amp;lt;ion-content has-bouncing=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;div class=&amp;quot;col&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;label class=&amp;quot;item item-input item-select&amp;quot;&amp;gt;&lt;br /&gt;
                        &amp;lt;div class=&amp;quot;input-label&amp;quot;&amp;gt;&lt;br /&gt;
                          {{ &#039;mma.notes.publishstate&#039; | translate }}&lt;br /&gt;
                        &amp;lt;/div&amp;gt;&lt;br /&gt;
                        &amp;lt;select ng-model=&amp;quot;note.publishstate&amp;quot; name=&amp;quot;publishstate&amp;quot;&amp;gt;&lt;br /&gt;
                            &amp;lt;option value=&amp;quot;personal&amp;quot;&amp;gt;{{ &#039;mma.notes.personalnotes&#039; | translate }}&amp;lt;/option&amp;gt;&lt;br /&gt;
                            &amp;lt;option value=&amp;quot;course&amp;quot;&amp;gt;{{ &#039;mma.notes.coursenotes&#039; | translate }}&amp;lt;/option&amp;gt;&lt;br /&gt;
                            &amp;lt;option value=&amp;quot;site&amp;quot;&amp;gt;{{ &#039;mma.notes.sitenotes&#039; | translate }}&amp;lt;/option&amp;gt;&lt;br /&gt;
                        &amp;lt;/select&amp;gt;&lt;br /&gt;
                    &amp;lt;/label&amp;gt;&lt;br /&gt;
                &amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;row&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;div class=&amp;quot;col&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;label class=&amp;quot;item item-input text&amp;quot;&amp;gt;&lt;br /&gt;
                        &amp;lt;textarea placeholder=&amp;quot;{{ &#039;mma.notes.note&#039; | translate }}&amp;quot; rows=&amp;quot;5&amp;quot; ng-model=&amp;quot;note.text&amp;quot; name=&amp;quot;text&amp;quot; lose-focus-on-return required=&amp;quot;required&amp;quot;&amp;gt;&amp;lt;/textarea&amp;gt;&lt;br /&gt;
                    &amp;lt;/label&amp;gt;&lt;br /&gt;
                &amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
            &amp;lt;div class=&amp;quot;padding&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;button type=&amp;quot;submit&amp;quot; class=&amp;quot;button button-block&amp;quot; ng-disabled=&amp;quot;processing || !note.text.length || note.text.length &amp;lt; 2&amp;quot;&amp;gt;&lt;br /&gt;
                    {{ &#039;mma.notes.addnewnote&#039; | translate }}&lt;br /&gt;
                &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-content&amp;gt;&lt;br /&gt;
    &amp;lt;/form&amp;gt;&lt;br /&gt;
&amp;lt;/ion-modal-view&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This file includes the code for the modal. As you can see the close button will call the closeModal() function meanwhile the Add a note button will submit the form that will invoke the addNote function&lt;br /&gt;
&lt;br /&gt;
For more information, refer to http://ionicframework.com/docs/api/service/$ionicModal/&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
[[Moodle Mobile Developing a plugin tutorial part 2]]&lt;br /&gt;
&lt;br /&gt;
[[Category: Tutorial]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Developing_a_plugin_tutorial_part_2&amp;diff=62161</id>
		<title>Moodle Mobile 2 (Ionic 1) Developing a plugin tutorial part 2</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Developing_a_plugin_tutorial_part_2&amp;diff=62161"/>
		<updated>2022-04-29T07:14:29Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 2 (Ionic 1)}}&lt;br /&gt;
&lt;br /&gt;
IMPORTANT: This documentation will be obsolete in May 2018, please [https://moodle.org/mod/forum/discuss.php?d=360412 read this announcement].&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
In this tutorial we are going to extend the notes add-on functionality&lt;br /&gt;
&lt;br /&gt;
== Add-on spec ==&lt;br /&gt;
&lt;br /&gt;
We want to develop a plugin that will display a new option at course level (at the same line that contents, participants and grades).&lt;br /&gt;
&lt;br /&gt;
This option will display all the course notes for all the participants in the course (it&#039;s suitable for teachers).&lt;br /&gt;
&lt;br /&gt;
A couple of screenshots with the desired result:&lt;br /&gt;
&lt;br /&gt;
{|&lt;br /&gt;
| [[File:MoodleMobile2Tutorial201.png|thumb|Initial  view]]&lt;br /&gt;
| [[File:MoodleMobile2Tutorial202.png|thumb|Note types view]]&lt;br /&gt;
| [[File:MoodleMobile2Tutorial203.png|thumb|Notes view]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Steps ==&lt;br /&gt;
&lt;br /&gt;
=== Set up your development environment ===&lt;br /&gt;
&lt;br /&gt;
Please, read [[Setting up your development environment for Moodle Mobile 2]]&lt;br /&gt;
&lt;br /&gt;
=== Set up your Moodle installation ===&lt;br /&gt;
&lt;br /&gt;
Enable debugging, disable caches, etc... the typical settings for developing.&lt;br /&gt;
&lt;br /&gt;
Enable the Mobile Service via Admin / Plugins / Web Services / Mobile&lt;br /&gt;
&lt;br /&gt;
=== Develop the Moodle Web Services you are going to need ===&lt;br /&gt;
&lt;br /&gt;
In this case we&#039;ll use an existing web service (core_notes_get_course_notes) that is already available in the Moodle Mobile app service, if you need custom functions you will need to develop a local plugin including there your new Web Services (see https://moodle.org/plugins/view/local_wstemplate) and create a custom service.&lt;br /&gt;
&lt;br /&gt;
=== Develop the Moodle Mobile add-on ===&lt;br /&gt;
&lt;br /&gt;
The full source code can be found here: https://github.com/moodlehq/moodlemobile2/tree/master/www/addons/notes&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;www/addons/notes/main.js&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
angular.module(&#039;mm.addons.notes&#039;, [])&lt;br /&gt;
&lt;br /&gt;
.config(function($stateProvider) {&lt;br /&gt;
&lt;br /&gt;
    $stateProvider&lt;br /&gt;
&lt;br /&gt;
    .state(&#039;site.notes-types&#039;, {&lt;br /&gt;
        url: &#039;/notes-types&#039;,&lt;br /&gt;
        views: {&lt;br /&gt;
            &#039;site&#039;: {&lt;br /&gt;
                templateUrl: &#039;addons/notes/templates/types.html&#039;,&lt;br /&gt;
                controller: &#039;mmaNotesTypesCtrl&#039;&lt;br /&gt;
            }&lt;br /&gt;
        },&lt;br /&gt;
        params: {&lt;br /&gt;
            course: null&lt;br /&gt;
        }&lt;br /&gt;
    })&lt;br /&gt;
&lt;br /&gt;
    .state(&#039;site.notes-list&#039;, {&lt;br /&gt;
        url: &#039;/notes-list&#039;,&lt;br /&gt;
        views: {&lt;br /&gt;
            &#039;site&#039;: {&lt;br /&gt;
                templateUrl: &#039;addons/notes/templates/list.html&#039;,&lt;br /&gt;
                controller: &#039;mmaNotesListCtrl&#039;&lt;br /&gt;
            }&lt;br /&gt;
        },&lt;br /&gt;
        params: {&lt;br /&gt;
            courseid: null,&lt;br /&gt;
            type: null&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
})&lt;br /&gt;
&lt;br /&gt;
.run(function($mmUserDelegate, $mmaNotesHandlers, $mmCoursesDelegate, $mmaNotes) {&lt;br /&gt;
&lt;br /&gt;
    // Register plugin on course list.&lt;br /&gt;
    $mmCoursesDelegate.registerPlugin(&#039;mmaNotes&#039;, function() {&lt;br /&gt;
&lt;br /&gt;
        if ($mmaNotes.isPluginViewNotesEnabled()) {&lt;br /&gt;
            return {&lt;br /&gt;
                icon: &#039;ion-ios-list&#039;,&lt;br /&gt;
                state: &#039;site.notes-types&#039;,&lt;br /&gt;
                title: &#039;mma.notes.notes&#039;&lt;br /&gt;
            };&lt;br /&gt;
        }&lt;br /&gt;
    });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we declare the plugin, and register the Plugin (this means that the plugin will be displayed in the course main menu.&lt;br /&gt;
&lt;br /&gt;
Note also that we are declaring the states (views) the plugin will support, in this case there are two views: &lt;br /&gt;
* notes-types: Is a list that display the different note types available (site, course, personal).&lt;br /&gt;
* notes-lists: that will display a list of notes for the type selected in the previous view.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;www/addons/notes/templates/types.html&#039;&#039;&#039; and &#039;&#039;&#039;www/addons/notes/templates/list.html&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
These files contains the templates used for the view, the first one is very simple since it only links to the different note types.&lt;br /&gt;
&lt;br /&gt;
The second one is more complex since requires a controller that is going to do all the logic for retrieving the note and prepare them to be displayed.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;www/addons/notes/controllers/types.js&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This is the controller that inject data in the types.html view, the code is pretty simple:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
angular.module(&#039;mm.addons.notes&#039;)&lt;br /&gt;
.controller(&#039;mmaNotesTypesCtrl&#039;, function($scope, $stateParams) {&lt;br /&gt;
    var course = $stateParams.course,&lt;br /&gt;
        courseid = course.id;&lt;br /&gt;
&lt;br /&gt;
    $scope.courseid = courseid;&lt;br /&gt;
&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It just make available in the $scope the courseid, that is required because is passed as a state parameter for the list view.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;www/addons/notes/controllers/list.js&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This controller is the one that will render in the list view the notes:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
.controller(&#039;mmaNotesListCtrl&#039;, function($scope, $stateParams, $mmUtil, $mmaNotes, $mmSite, $translate) {&lt;br /&gt;
&lt;br /&gt;
    var courseid = $stateParams.courseid,&lt;br /&gt;
        type = $stateParams.type;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Here we retrieved the state parameters (the course id and the type of notes) that were declared in the types.html view.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
    function fetchNotes(refresh) {&lt;br /&gt;
        return $mmaNotes.getNotes(courseid, refresh).then(function(notes) {&lt;br /&gt;
            notes = notes[type + &#039;notes&#039;];&lt;br /&gt;
&lt;br /&gt;
            return $mmaNotes.getNotesUserData(notes, courseid).then(function(notes) {&lt;br /&gt;
                $scope.notes = notes;&lt;br /&gt;
            });&lt;br /&gt;
&lt;br /&gt;
        }, function(message) {&lt;br /&gt;
            $mmUtil.showErrorModal(message);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The fetchNotes function is responsible of using the Notes service getNotes to retrieve the data, note that we use an additional function getNotesUserData to retrieve the full user profile information (since via getNotes we only get the userid)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
fetchNotes().then(function() {&lt;br /&gt;
        // Add log in Moodle.&lt;br /&gt;
        $mmSite.write(&#039;core_notes_view_notes&#039;, {&lt;br /&gt;
            courseid: courseid,&lt;br /&gt;
            userid: 0&lt;br /&gt;
        });&lt;br /&gt;
    })&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
After retrieving the notes, we do logging in Moodle (so the actions are registered in the web interface)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
    $scope.refreshNotes = function() {&lt;br /&gt;
        fetchNotes(true).finally(function() {&lt;br /&gt;
            $scope.$broadcast(&#039;scroll.refreshComplete&#039;);&lt;br /&gt;
        });&lt;br /&gt;
    };&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This function is triggered by the &amp;quot;Pull down to refresh&amp;quot; action in order to refresh the view (empty the cache), note that the true parameter in the fetchNotes function means to not use cache.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;www/addons/notes/services/notes.js&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The notes services holds all the main plugin logic, it&#039;s responsible of checking if the plugin is available, retrieve information for the server and format the information so it can be rendered in the view by the controller.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
    self.isPluginViewNotesEnabled = function() {&lt;br /&gt;
        var infos;&lt;br /&gt;
&lt;br /&gt;
        if (!$mmSite.isLoggedIn()) {&lt;br /&gt;
            return false;&lt;br /&gt;
        } else if (!$mmSite.canUseAdvancedFeature(&#039;enablenotes&#039;)) {&lt;br /&gt;
            return false;&lt;br /&gt;
        } else if (!$mmSite.wsAvailable(&#039;core_notes_get_course_notes&#039;)) {&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    };&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Here we checked if the plugin is enabled, the user must logged in, enablenotes should be enabled in the Moodle global settings and the core_notes_get_course_notes function should be available.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
    self.getNotes = function(courseid, refresh) {&lt;br /&gt;
&lt;br /&gt;
        $log.debug(&#039;Get notes for course &#039; + courseid);&lt;br /&gt;
&lt;br /&gt;
        var data = {&lt;br /&gt;
                courseid : courseid&lt;br /&gt;
            },&lt;br /&gt;
            presets = {};&lt;br /&gt;
        if (refresh) {&lt;br /&gt;
            presets.getFromCache = false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $mmSite.read(&#039;core_notes_get_course_notes&#039;, data, presets);&lt;br /&gt;
    };&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Here we retrieve the notes information from Moodle via Web Services, note that the refresh function is used for invalidating the cache (for example, when the user performs a pull down to refresh).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
    self.getNotesUserData = function(notes, courseid) {&lt;br /&gt;
        var promises = [];&lt;br /&gt;
&lt;br /&gt;
        angular.forEach(notes, function(note) {&lt;br /&gt;
            var promise = $mmUser.getProfile(note.userid, courseid, true);&lt;br /&gt;
            promises.push(promise);&lt;br /&gt;
            promise.then(function(user) {&lt;br /&gt;
                note.userfullname = user.fullname;&lt;br /&gt;
                note.userprofileimageurl = user.profileimageurl;&lt;br /&gt;
            }, function() {&lt;br /&gt;
                // Error getting profile. Set default data.&lt;br /&gt;
                return $translate(&#039;mma.notes.userwithid&#039;, {id: note.userid}).then(function(str) {&lt;br /&gt;
                    note.userfullname = str;&lt;br /&gt;
                });&lt;br /&gt;
            });&lt;br /&gt;
        });&lt;br /&gt;
        return $q.all(promises).then(function() {&lt;br /&gt;
            return notes;&lt;br /&gt;
        });&lt;br /&gt;
    };&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Since getNotes returns only userids, we need to retrieve the complete user information in order to be able to display his full name and profile image. We use the $mmUser service getProfile function.&lt;br /&gt;
&lt;br /&gt;
[[Category: Mobile]]&lt;br /&gt;
[[Category: Tutorial]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Plugins_Development&amp;diff=62160</id>
		<title>Moodle Mobile 2 (Ionic 1) Plugins Development</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Plugins_Development&amp;diff=62160"/>
		<updated>2022-04-29T07:14:27Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 2 (Ionic 1)}}&lt;br /&gt;
&lt;br /&gt;
IMPORTANT: This documentation will be obsolete in May 2018, please [https://moodle.org/mod/forum/discuss.php?d=360412 read this announcement].&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
Plugins (known as Moodle Mobile 2 addons) allow developers to extend the app functionalities.&lt;br /&gt;
&lt;br /&gt;
An addon is a subdirectory that implements a set of required functionalities. For those familiars with AngularJS, addones are AngularJS modules.&lt;br /&gt;
&lt;br /&gt;
IMPORTANT: Read carefully this document ([[Moodle Mobile Customization]]) before you start developing!&lt;br /&gt;
&lt;br /&gt;
Please, note also that this documentation is oriented to Moodle Mobile specific APIs, before starting developing you should get familiar with AngularJS and Ionic, here you have some resources:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ionic&#039;&#039;&#039;&lt;br /&gt;
* http://ionicframework.com - Official Ionic website&lt;br /&gt;
* https://www.airpair.com/ionic-framework/posts/the-definitive-ionic-starter-guide - Definitive ionic starter guide&lt;br /&gt;
* http://mcgivery.com/100-ionic-framework-resources/ - +100 Ionic resources&lt;br /&gt;
* https://github.com/juarezpaf/ionic-adventures?utm_source=mobilewebweekly&amp;amp;utm_medium=email - More ionic resources&lt;br /&gt;
* http://codepen.io/ionic/public-list/ - Ionic sample code lists&lt;br /&gt;
* https://www.youtube.com/channel/UChYheBnVeCfhCmqZfCUdJQw - YouTube channel from Ionic&lt;br /&gt;
* http://www.manning.com/wilken/ - Ionic in action book&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Angular&#039;&#039;&#039;&lt;br /&gt;
* https://angularjs.org - The official documentation for AngularJS&lt;br /&gt;
* http://manning.com/bford - AngularJS in Action book&lt;br /&gt;
* http://manning.com/aden - AngularJS in Depth book&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cordova&#039;&#039;&#039;&lt;br /&gt;
* http://cordova.apache.org - Official Cordova site&lt;br /&gt;
* http://plugins.cordova.io - Cordova official plugin registry.&lt;br /&gt;
* http://ngcordova.com - ngCordova AngularJS wrappers for cordova&lt;br /&gt;
&lt;br /&gt;
== Moodle Mobile 2 addons ==&lt;br /&gt;
&lt;br /&gt;
Each addon can have services, controllers, templates and lang files. The addon needs  to specify a main.js file to initialize the addon and to register itself into one or more delegates (this determines where will the plugin be shown).&lt;br /&gt;
&lt;br /&gt;
Naming conventions for addons:&lt;br /&gt;
* The module name for addons needs to be mm.addons.addonname and it should be defined in the addon main.js.&lt;br /&gt;
* All the services names inside an addon need to start with $mma, followed by the service name in camel-case. For example, $mmaForumData.&lt;br /&gt;
* The controllers names need to start with mma (without dollar), followed by the controller name in camel-case. The controller name needs to contain Ctrl to easily identify it as a controller. For example, mmaForumListCtrl.&lt;br /&gt;
&lt;br /&gt;
The app comes with a predefined set of addons: messages, forum, notifications, etc. All these addons are inside the www/addons folder. &lt;br /&gt;
&lt;br /&gt;
An addon can be shown in several places. The app has one delegate per each place an addon can be shown, so each addon can register itself to any set of delegates.&lt;br /&gt;
&lt;br /&gt;
You can see the list of available delegates [https://docs.moodle.org/dev/Moodle_Mobile#Addons in this section].&lt;br /&gt;
&lt;br /&gt;
=== Structure of an addon ===&lt;br /&gt;
&lt;br /&gt;
If you are not familiar with AngularJS, please, read the [https://docs.angularjs.org/guide AngularJS] guide to a complete understanding of the different terms used in this document.&lt;br /&gt;
&lt;br /&gt;
Forum addon directory:&lt;br /&gt;
&lt;br /&gt;
 controllers/&lt;br /&gt;
  discussion.js&lt;br /&gt;
  discussions.js&lt;br /&gt;
 lang/&lt;br /&gt;
   en.json&lt;br /&gt;
 services/&lt;br /&gt;
  forum.js    &lt;br /&gt;
 templates/&lt;br /&gt;
  discussion.html&lt;br /&gt;
  discussions.html&lt;br /&gt;
 main.js&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;controllers and templates&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The controllers directory contains the module controllers, usually you are going to have there a controller per view and per template.&lt;br /&gt;
&lt;br /&gt;
In the forum example, you can see that there are two controllers and two templates:&lt;br /&gt;
&lt;br /&gt;
* discussions.js|html for displaying the complete list of discussions of a forum in a course&lt;br /&gt;
* discussion.js|html   for displaying a single discussion.&lt;br /&gt;
&lt;br /&gt;
Each controller is responsible of rendering the view, for example, the discussions.js will fetch and display the list of discussions of the forum using the helper service defined in forum.js&lt;br /&gt;
&lt;br /&gt;
The controller will also implement additional functionalities like support to &amp;quot;Pull down to refresh&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;lang&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
The lang directory contain the language file with the translated strings.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;services&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This directory contains the app services, usually for non-complex addons you will find a single file inside containing all the code logic used for providing the main functionality of the addon like the code for retrieving the discussions via Moodle Web Services.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;main.js&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This is a mandatory file, it:&lt;br /&gt;
* Creates the AngularJS module&lt;br /&gt;
* Declares all the state routes the plugin is going to user. Attaching a controller and template for each different state.&lt;br /&gt;
* Initializes the addon and register it into one or more delegates (this determines where the plugin will be displayed in the app).&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Moodle Mobile Developing a plugin tutorial]]&lt;br /&gt;
* [[Moodle Mobile Developing a plugin tutorial part 2]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Remote_add-ons&amp;diff=62159</id>
		<title>Moodle Mobile 2 (Ionic 1) Remote add-ons</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Remote_add-ons&amp;diff=62159"/>
		<updated>2022-04-29T07:14:24Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 2 (Ionic 1)}}&lt;br /&gt;
{{Moodle 3.1}}&lt;br /&gt;
Only available for Moodle 3.1 onwards.&lt;br /&gt;
&lt;br /&gt;
IMPORTANT: This documentation will be obsolete in May 2018, please [https://moodle.org/mod/forum/discuss.php?d=360412 read this announcement].&lt;br /&gt;
&lt;br /&gt;
A Moodle Mobile remote add-on is the mobile app version of a Moodle plugin that will be loaded when a user accesses the site on the app.&lt;br /&gt;
&lt;br /&gt;
Moodle Mobile remote add-ons are stored within a Moodle plugin and are downloaded and lazy-loaded by the Mobile app in every site that uses them.&lt;br /&gt;
&lt;br /&gt;
Moodle plugins can include several Mobile add-ons. They can also declare dependencies between plugins.&lt;br /&gt;
&lt;br /&gt;
We can say that a remote add-on is a special version of a Moodle Mobile add-on. The main differences are:&lt;br /&gt;
* Mobile remote add-ons are placed inside Moodle plugins (outside the Mobile app).&lt;br /&gt;
* Mobile remote add-ons language files must include the whole component prefix.&lt;br /&gt;
* Mobile remote add-ons uses special placeholders for paths inside Javascript code.&lt;br /&gt;
&lt;br /&gt;
If you do not know how to develop for the Moodle Mobile app at all, you might find it useful to start with a tutorial (refer back to this page for more detailed information): [[Moodle Mobile remote add-on tutorial]]&lt;br /&gt;
&lt;br /&gt;
== How to create a remote add-on ==&lt;br /&gt;
&lt;br /&gt;
Remote add-ons must be developed first as standard Moodle Mobile add-ons and then packaged as a remote add-on.&lt;br /&gt;
&lt;br /&gt;
These are the main required steps:&lt;br /&gt;
&lt;br /&gt;
* Develop the required Moodle Web Services&lt;br /&gt;
* Develop a standard Moodle Mobile add-on&lt;br /&gt;
* Package the Moodle Mobile add-on as a remote add-on&lt;br /&gt;
* Include the remote add-on in your Moodle plugin&lt;br /&gt;
&lt;br /&gt;
=== Web Services ===&lt;br /&gt;
&lt;br /&gt;
Most of Mobile add-ons require Web Services to work, but in some cases (like add-ons implementing question types) it won&#039;t be necessary.&lt;br /&gt;
&lt;br /&gt;
Please refer to [[Web services]] documentation, you have an example here: https://github.com/jleyva/moodle-mod_certificate/blob/CONTRIB-6313/classes/external.php&lt;br /&gt;
&lt;br /&gt;
Once the Web Services are developed, you must include them into the Mobile (and local_mobile) service using the &amp;quot;services&amp;quot; field when declaring external functions, see: https://github.com/jleyva/moodle-mod_certificate/blob/CONTRIB-6313/db/services.php&lt;br /&gt;
&lt;br /&gt;
Remember that this is only available from Moodle 3.1 onwards.&lt;br /&gt;
&lt;br /&gt;
=== Mobile add-on ===&lt;br /&gt;
&lt;br /&gt;
Refer to [[Moodle Mobile Plugins Development]]&lt;br /&gt;
&lt;br /&gt;
If your addon registers any store to the site database ([https://github.com/moodlehq/moodlemobile2/blob/master/www/addons/calendar/services/calendar.js#L19 example]), it must execute the following code in a &#039;&#039;.run&#039;&#039; function:&lt;br /&gt;
&lt;br /&gt;
 if ($mmSite.isLoggedIn() &amp;amp;&amp;amp; $mmSite.reloadDb) {&lt;br /&gt;
     $mmSite.reloadDb();&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Remote add-on packaging ===&lt;br /&gt;
&lt;br /&gt;
==== Automatic packaging ====&lt;br /&gt;
&lt;br /&gt;
The MoodleMobile app comes with a gulp task to package remote addons. This task is in the file [https://github.com/moodlehq/moodlemobile2/blob/master/gulpfile.js gulpfile.js located in the root folder].&lt;br /&gt;
&lt;br /&gt;
In order to package the addon, go to the project&#039;s root folder (where the gulpfile.js is located) and run the following command:&lt;br /&gt;
&lt;br /&gt;
 gulp remoteaddon -p {PATHTOADDON}&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
 gulp remoteaddon -p www/addons/mod/certificate&lt;br /&gt;
&lt;br /&gt;
This command should create a ZIP file inside the addon folder. This ZIP should contain all the files required for your remote addon to work.&lt;br /&gt;
&lt;br /&gt;
Options accepted by the command:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;--path or -p&#039;&#039;&#039;: Required parameter. Path to the addon folder.&lt;br /&gt;
* &#039;&#039;&#039;--jspath or -jsp&#039;&#039;&#039;: Optional. The path to replace in the Javascript files. All the matches found will be replaced by &#039;&#039;$ADDONPATH$&#039;&#039; (see [https://docs.moodle.org/dev/Moodle_Mobile_Remote_add-ons#Manual_packaging Manual packaging] for more info). If not defined we will use the same value as &amp;quot;path&amp;quot; param.&lt;br /&gt;
* &#039;&#039;&#039;--output or -o&#039;&#039;&#039;: Optional. Path to the output ZIP file. If not defined, the ZIP will be stored in the addon&#039;s folder.&lt;br /&gt;
&lt;br /&gt;
==== Manual packaging ====&lt;br /&gt;
&lt;br /&gt;
Some requirements need to be met to make the remote add-on work in the app. These are the steps you need to follow in order to make it work:&lt;br /&gt;
&lt;br /&gt;
* Place all the JavaScript code of your addon in a single file named &#039;&#039;addon.js&#039;&#039;. This file needs to be in your addon&#039;s root folder.&lt;br /&gt;
* In this &#039;&#039;addon.js&#039;&#039;, replace the paths to your addon folder with &#039;&#039;$ADDONPATH$&#039;&#039;. For example, if you have&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;templateUrl: &#039;addons/myaddon/templates/index.html&#039;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
 ...&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;$scope.icon = &#039;addons/myaddon/icon.gif&#039;;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
you should set it like this:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;templateUrl: &#039;$ADDONPATH$/templates/index.html&#039;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
 ...&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;$scope.icon = &#039;$ADDONPATH$/icon.gif&#039;;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
* Add your addon&#039;s prefix to language strings. That is, if you have a string like this:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&amp;quot;getcertificate&amp;quot;: &amp;quot;Get your certificate&amp;quot;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
you need to set it like this:&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;&amp;quot;mma.mod_certificate.getcertificate&amp;quot;: &amp;quot;Get your certificate&amp;quot;.&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
It&#039;s important that language files are inside a &amp;quot;&#039;&#039;lang&#039;&#039;&amp;quot; folder in your addon&#039;s root folder.&lt;br /&gt;
* It&#039;s required to have 1 language file for each language supported by the app. If you don&#039;t want to translate to a certain language then you must create a file with an empty JSON for that language. [https://github.com/moodlehq/moodlemobile-phonegapbuild/tree/master/build/lang Click here] to see the list of languages supported by the app.&lt;br /&gt;
* Place all the styles of your addon in a single file named &#039;&#039;styles.css&#039;&#039;. This file needs to be in your addon&#039;s root folder. Please notice you don&#039;t need to use &#039;&#039;$ADDONPATH$&#039;&#039; in here, all the paths will be relative to the addon&#039;s root folder.&lt;br /&gt;
&lt;br /&gt;
Once all this is done you need to create a zip file with your addon files. Please make sure that the files &#039;&#039;addon.js&#039;&#039; and &#039;&#039;styles.css&#039;&#039; are in the root of this zip file.&lt;br /&gt;
&lt;br /&gt;
See https://github.com/jleyva/moodle-mod_certificate/tree/CONTRIB-6313/mobile for a zip file example for the Certificate module.&lt;br /&gt;
&lt;br /&gt;
=== Remote add-on publishing ===&lt;br /&gt;
&lt;br /&gt;
Once you have a zip file containing the remote add-on, you must include it into the Moodle plugin.&lt;br /&gt;
&lt;br /&gt;
Please, refer to [[Activity_modules#mobile.php]] for instructions. Basically you will need to create a db/mobile.php and place the zip file into mobile/addonname.zip&lt;br /&gt;
&lt;br /&gt;
== Known issues ==&lt;br /&gt;
&lt;br /&gt;
* You cannot use Cordova or 3rd party plugins not used by the app. You can see the list of supported plugins here: https://github.com/moodlehq/moodlemobile-phonegapbuild/blob/master/config.xml#L112&lt;br /&gt;
* When creating/editing db/mobile.php during development you must bump the version or purge caches. See MDL-58590&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* List of plugins currently providing mobile app support: [https://moodle.org/plugins/browse.php?list=award&amp;amp;id=6 Moodle plugins directory: Award: mobile app support]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Themes&amp;diff=62158</id>
		<title>Moodle Mobile 2 (Ionic 1) Themes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Themes&amp;diff=62158"/>
		<updated>2022-04-29T07:14:20Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 2 (Ionic 1)}}&lt;br /&gt;
&lt;br /&gt;
IMPORTANT: This documentation will be obsolete in May 2018, please [https://moodle.org/mod/forum/discuss.php?d=360412 read this announcement].&lt;br /&gt;
&lt;br /&gt;
The Mobile app can retrieve your custom styles from your Moodle site. Since is an HTML5 app, you can apply safely CSS3 styles.&lt;br /&gt;
&lt;br /&gt;
In your Moodle installation go to &amp;quot;Administration &amp;gt; Site administration &amp;gt; Mobile app &amp;gt; Appearance&amp;quot; and enter in the mobilecssurl field a valid URL pointing to a CSS file containing your custom styles (theme).&lt;br /&gt;
&lt;br /&gt;
The CSS should be placed inside your Moodle installation (in your custom theme or inside a local plugin).&lt;br /&gt;
&lt;br /&gt;
Notice that on the first time a user opens the app, he will see the default &amp;quot;orange&amp;quot; style. Your custom styles will be applied once the user has added your site in the app.&lt;br /&gt;
&lt;br /&gt;
Notice also that styles comes from a site, there is no way for changing the Add site / Manage account styles.&lt;br /&gt;
&lt;br /&gt;
== Example of a  custom theme (CSS file) ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;css&amp;quot;&amp;gt;&lt;br /&gt;
/* Un-comment the following, for initial RTL UI support.&lt;br /&gt;
.scroll-content {&lt;br /&gt;
   direction: rtl; &lt;br /&gt;
   text-align: right;&lt;br /&gt;
}&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
 .bar-side-menu {&lt;br /&gt;
   background-color: blue;&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 .bar-content {&lt;br /&gt;
   background-color: blue;&lt;br /&gt;
 }&lt;br /&gt;
 &lt;br /&gt;
 .button.button-positive {&lt;br /&gt;
   border-color: blue;&lt;br /&gt;
   background-color: blue;&lt;br /&gt;
    color: #fff;&lt;br /&gt;
 } &lt;br /&gt;
 &lt;br /&gt;
 .item-divider {&lt;br /&gt;
   background-color: #fafafa;&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
Please, read [[Setting up your development environment for Moodle Mobile 2]] in order to set up a local environment for testing your custom CSS file, you should also read this document: [[Moodle Mobile development using Chrome or Chromium]].&lt;br /&gt;
&lt;br /&gt;
== Styling specific pages ==&lt;br /&gt;
&lt;br /&gt;
Moodle Mobile 2.9 onwards add a specific class for every page in the app (in the ion-content tag), in this way you can apply specific styles for pages.&lt;br /&gt;
&lt;br /&gt;
For example, if you inspect the app you&#039;ll see things like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;nowiki&amp;gt;&amp;lt;ion-content class=&amp;quot;mm-site_mm_courses&amp;quot;&amp;gt;...&amp;lt;/ion-content&amp;gt;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Files referenced in the CSS files ==&lt;br /&gt;
&lt;br /&gt;
Moodle Mobile 3.1.0 onwards automatically download the files referenced in the CSS for offline usage. Please notice that data URLs aren&#039;t downloaded.&lt;br /&gt;
&lt;br /&gt;
== CSS and files synchronization ==&lt;br /&gt;
&lt;br /&gt;
The CSS file and the referenced files will be downloaded or updated when the user starts the app or accesses your site. Please notice that the downloaded files are updated only if the file URL changes or the user synchronizes the site data in App Settings.&lt;br /&gt;
&lt;br /&gt;
If you want to update a file without changing the file location you can add a prefix to the file URL. Example: {SITEURL}/myimage.png&#039;&#039;&#039;#1&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Useful selectors ==&lt;br /&gt;
&lt;br /&gt;
Here&#039;s a list of useful selectors to customize the application. &lt;br /&gt;
 You can always navigate to: http://prototype.moodle.net/mobile/webapp (which is connected to https://school.demo.moodle.net) and use the username &amp;quot;teacher&amp;quot; and password &amp;quot;moodle&amp;quot; to log in, and then use the browser&#039;s developer tools (F12 or Inspect) to figure out all the IDs and classes for your CSS selectors.&lt;br /&gt;
&lt;br /&gt;
=== App header ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Class &#039;&#039;.bar-header&#039;&#039;&#039;&#039;&#039;: To customize the header of the application and the side menu (shown in all screens).&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Class &#039;&#039;.bar-side-menu&#039;&#039;&#039;&#039;&#039;: To customize only the side menu header..&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Class &#039;&#039;.bar-content&#039;&#039;&#039;&#039;&#039;: To customize only the content header, not the side menu.&lt;br /&gt;
&lt;br /&gt;
=== Side menu ===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Element &#039;&#039;ion-side-menu&#039;&#039;&#039;&#039;&#039;: All the elements shown in a side menu (either left or right) are inside this element.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Attribute &#039;&#039;side&#039;&#039;&#039;&#039;&#039;: Useful to distinguish between left and right side menu. Example:&lt;br /&gt;
&lt;br /&gt;
 ion-side-menu[side=&amp;quot;left&amp;quot;]&lt;br /&gt;
 ion-side-menu[side=&amp;quot;right&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;IDs &#039;&#039;mm-side-menu-left&#039;&#039; and &#039;&#039;mm-side-menu-right&#039;&#039;&#039;&#039;&#039;: Also useful to distinguish between left and right side menu. Example:&lt;br /&gt;
&lt;br /&gt;
 #mm-side-menu-left&lt;br /&gt;
 #mm-side-menu-right&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Class &#039;&#039;.item&#039;&#039;&#039;&#039;&#039;: Every item in the side menu has this class.  This class is common in the whole app, please see Ionic styles. For example, if you want to apply a style to all the items in the side menu you can use the selector:&lt;br /&gt;
&lt;br /&gt;
 ion-side-menu li .item, ion-side-menu li.item&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Class &#039;&#039;.item-divider&#039;&#039;&#039;&#039;&#039;: To customize the divider that splits the side menu list in two. This class is common in the whole app, please see Ionic styles. &lt;br /&gt;
&lt;br /&gt;
=== Icons ===&lt;br /&gt;
&lt;br /&gt;
The app usually uses Ionic icons, identified by a class. If you want to change an icon to use your own you should search the class that identifies the icon (usually starts with ion-) and override the content.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;.ion-ionic:before {&lt;br /&gt;
   content: &amp;quot;\f3ea&amp;quot;;&lt;br /&gt;
 }&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can see the whole list of Ionic icons [http://ionicons.com in the Ionicons web].&lt;br /&gt;
&lt;br /&gt;
You can see the unicode value for a certain Ionic icon near the icon name in [https://raw.githubusercontent.com/moodlehq/moodlemobile-phonegapbuild/master/lib/ionic/fonts/ionicons.svg this file].&lt;br /&gt;
&lt;br /&gt;
=== Ionic styles ===&lt;br /&gt;
&lt;br /&gt;
The application is built using the Ionic framework, and so it contains a lot of elements and styles defined by Ionic.&lt;br /&gt;
&lt;br /&gt;
You can see a list of common Ionic elements in the [http://ionicframework.com/docs/components/ Ionic CSS Components web].&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [http://www.slideshare.net/juanleyva/creating-moodle-mobile-remote-themes Creating Moodle Mobile themes] - A presentation in the MoodleMoot UK 2016&lt;br /&gt;
* [https://www.slideshare.net/DaniPalouSala/creating-moodle-mobile-remote-themes-moodle-moot-us-2016 Same presentation updated for the MoodleMoot USA 2016] - Includes an example to add a logo at the top of the side menu&lt;br /&gt;
* http://caniuse.com/ Support tables for CSS3 styles (including Mobile browsers)&lt;br /&gt;
&lt;br /&gt;
[[Category: Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2.0_spec&amp;diff=62157</id>
		<title>Moodle Mobile 2.0 spec</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2.0_spec&amp;diff=62157"/>
		<updated>2022-04-29T07:14:17Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{obsolete}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;For development documentation about Moodle Mobile 2 please refer to: [[Moodle Mobile]]&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Introduction==&lt;br /&gt;
This document is about the future of the Moodle Mobile app, focusing on technical aspects exclusively.&lt;br /&gt;
&lt;br /&gt;
This document describes the current problems and improvements needed by the app. It also discusses improvements to be done in the development process of the app.&lt;br /&gt;
&lt;br /&gt;
It has been a long time since the first version of the app was designed; in 2012 the mobile scene was very different than today:  Android 2.1, 2.3 and iOS 5 were the most used versions, HTML5 support in general terms was pretty limited, there were no frameworks for creating hybrid apps, Phonegap was in earlier versions, etc.&lt;br /&gt;
&lt;br /&gt;
After an initial analysis, we chose to create our app using mainly jquery and backbone (avoiding all the current framework, libraries due to the performance problems). At the time AngularJS was also an option, but we discarded it since it wasn’t as popular as it is now—no one was using it for creating mobile apps back then: [https://docs.google.com/document/d/15QQaBO176IDI97mfT0Fa8nWPeejOx3uO_EnM4fANPmA/edit https://docs.google.com/document/d/15QQaBO176IDI97mfT0Fa8nWPeejOx3uO_EnM4fANPmA/edit]&lt;br /&gt;
&lt;br /&gt;
Today, Android 4 and iOS 7 are the dominant operating systems/versions. HTML5 support on those devices has improved significantly and there are several frameworks oriented to create  hybrid mobile apps focused on performance.&lt;br /&gt;
&lt;br /&gt;
The app has currently some technical problems that would be resolved with that framework (although not all). Some problems are related to the user interface, layout/design and usability of the application; others are more specific for javascript/devices/platforms and some problems will not be resolved choosing a different framework.&lt;br /&gt;
&lt;br /&gt;
* Design, usability:&lt;br /&gt;
::All the design and usability patterns are done from scratch; we need a framework that will take care of that so nothing breaks in major platforms changes (as already happened with iOS 8, Android 4.4, Android 5)&lt;br /&gt;
* Touch events responsiveness: &lt;br /&gt;
::The current app does not deal with that satisfactory; we need to migrate to a library supporting touch events natively, capable of detecting the device correctly for using the correct events&lt;br /&gt;
* Using Chrome simulators:&lt;br /&gt;
::Due to the problem related to touch events, we can’t use Chrome simulator for testing the apps&lt;br /&gt;
* Web components:&lt;br /&gt;
::We do not use any library for web components (like lists) and every time we need to add something new we have to do it from scratch, testing that is works “properly” in all the devices we support&lt;br /&gt;
* Support for CSP:&lt;br /&gt;
::The app uses Backbone template system which is not compatible with CSP&lt;br /&gt;
* Infinite scroll:&lt;br /&gt;
::This can be categorized as a web component&lt;br /&gt;
* Pull down refresh:&lt;br /&gt;
::This can be categorized as a web component too&lt;br /&gt;
* Animations performance:&lt;br /&gt;
::We do not use hardware/CSS3 animations&lt;br /&gt;
* Unit testing:&lt;br /&gt;
::There is no support for unit tests&lt;br /&gt;
* Security:&lt;br /&gt;
::The app uses a global object that has access to the global scope and the plugins scope&lt;br /&gt;
* RTL languages support:&lt;br /&gt;
::The app doesn’t support RTL right now&lt;br /&gt;
* Build process:&lt;br /&gt;
::We should start doing some automatization of common tasks using Grunt, etc. Some tasks are currently done in shell scripts, php, but there is no process of continuous integration/testings&lt;br /&gt;
* Storage:&lt;br /&gt;
::The app uses localStorage limited to 5MB&lt;br /&gt;
* Background processes:&lt;br /&gt;
::The current app has some memory problems due to the current sync process that is done in the main thread&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In this document I’m not going to do an analysis of all the available frameworks. There are tons of articles regarding this issue in Internet, we have chosen these two:&lt;br /&gt;
&lt;br /&gt;
* About mobile specific frameworks:&lt;br /&gt;
::[http://www.agingcoder.com/programming/2014/10/16/mobile-frameworks-onsenui/ http://www.agingcoder.com/programming/2014/10/16/mobile-frameworks-onsenui/]&lt;br /&gt;
* About javascript frameworks:&lt;br /&gt;
::[http://www.airpair.com/js/javascript-framework-comparison?utm_source=javascriptweekly&amp;amp;utm_medium=email http://www.airpair.com/js/javascript-framework-comparison?utm_source=javascriptweekly&amp;amp;utm_medium=email]&lt;br /&gt;
&lt;br /&gt;
Also, I’ve been in some local events related to Mobile frameworks (locals meetup in Barcelona, BarcelonaJS group, AngularJS Barcelona group) talking with people and analyzing the advantages and disadvantages of different mobile frameworks.&lt;br /&gt;
&lt;br /&gt;
In conclusion, it seems that Ionic framework (based on AngularJS) is the right choice.&lt;br /&gt;
&lt;br /&gt;
==Reasons to use AngularJS/ionic framework==&lt;br /&gt;
&lt;br /&gt;
* Object observer webkit implementation!!&lt;br /&gt;
::[http://georgestefanis.com/blog/2014/03/25/object-observe-ES7.html http://georgestefanis.com/blog/2014/03/25/object-observe-ES7.html]&lt;br /&gt;
* AngularJS is a framework that supports all the functionalities currently covered by 3rd party libs&lt;br /&gt;
* It’s focused in hybrid apps with Phonegap/Cordova&lt;br /&gt;
* Components are tested in different Android and iOS versions&lt;br /&gt;
* It’s very popular (big developer base): 20,000 aps per month are using it&lt;br /&gt;
* Huge knowledge base, 1400 questions in stackoverflow&lt;br /&gt;
* Because AngularJS was chosen by the Ionic framework “rival” Onsen components&lt;br /&gt;
* AngularJS is supported by Google&lt;br /&gt;
* It supports CSP (https://developer.mozilla.org/en-US/docs/Web/Security/CSP) so it’s a framework valid for creating Firefox OS compatible apps&lt;br /&gt;
* Interesting functionalities: Unit tests coverage (Jasmine), CSP support, infinite scroll, pull down refresh, hardware accelerated transitions/effects,&lt;br /&gt;
* MVC - Using directives (no more DOM manipulation):&lt;br /&gt;
::It will reduce the code base to maintain&lt;br /&gt;
::Ionic comes with emulators and extremely useful build and testing tools; we won’t need our simulator anymore&lt;br /&gt;
* Very good integration with GruntJS, Yeoman, Istambul, Jasmine etc.&lt;br /&gt;
&lt;br /&gt;
===Disadvantages===&lt;br /&gt;
* Steep learning curve of AngularJS. For newcomers, AngularJS is a very popular framework between javascript developers, it can be easier to find AngularJS developers than developers knowing all the libraries currently used by the app.&lt;br /&gt;
* In older devices the app may run slow (but according to the last Moodle Mobile survey, users of Moodle are mostly using new devices)&lt;br /&gt;
* Angular is going to be rewritten for version 2.&lt;br /&gt;
* Ionic is still beta (it was alpha a couple of months ago)&lt;br /&gt;
* Angular only works for Android 4 and above. Firefox OS and Windows Phone seem not to be officially supported (although they are in the roadmap)&lt;br /&gt;
&lt;br /&gt;
===Conclusions===&lt;br /&gt;
* Ionic seems to be the best candidate as framework for the app.&lt;br /&gt;
* It’s popular (don’t forget that Moodle popularity is partially due to PHP we don’t have to forget that Moodle becomes so popular by the use of PHP), it uses AngularJS that has a big learning curve (although due to its popularity there is plenty of documentation and javascript developers using it).&lt;br /&gt;
* Moodle Mobile 2 release is planned for July/August 2015, so there will be less older devices and Ionic will not be a beta anymore. Angular 2 may have been published by then (and supported by Ionic) but even if it wasn’t, Google has more than a thousand of apps using Angular 1, so it will be supported for a few more years: http://eisenbergeffect.bluespire.com/all-about-angular-2-0/?utm_source=javascriptweekly&amp;amp;utm_medium=email&lt;br /&gt;
&lt;br /&gt;
==Roadmap==&lt;br /&gt;
* In order not to stop the current development of the app ([http://www.joelonsoftware.com/articles/fog0000000069.html http://www.joelonsoftware.com/articles/fog0000000069.html]), a small team should do the migration in parallel.&lt;br /&gt;
* An initial working prototype with static data should be implemented displaying all the current features of the app, supporting both phones and tablets.&lt;br /&gt;
::This prototype must be validated by Martin and Barbara, and afterwards a new phase of technical analysis of how to migrate the current architecture should start. This analysis should evaluate (among others):&lt;br /&gt;
* How to migrate the current plugin system to the AngularJS modules system&lt;br /&gt;
* How to implement the current core api function that uses jQuery (mainly the ajax requests) (use factories, services, etc..)&lt;br /&gt;
* Which storage system to use (keep localStorage, indexdb…)&lt;br /&gt;
* Background processes using webworkers (now that is better supported in modern devices)&lt;br /&gt;
* Continuous integration, testing, automatic tasks…&lt;br /&gt;
&lt;br /&gt;
== References, interesting articles ==&lt;br /&gt;
&lt;br /&gt;
Large apps:&lt;br /&gt;
http://www.pseudobry.com/building-large-apps-with-angular-js/&lt;br /&gt;
https://gocardless.com/blog/building-a-large-angular-application/&lt;br /&gt;
https://github.com/angular-app/angular-app&lt;br /&gt;
&lt;br /&gt;
https://github.com/angular/angular.js/wiki/Projects-using-AngularJS&lt;br /&gt;
&lt;br /&gt;
Tutorials:&lt;br /&gt;
https://thinkster.io/angulartutorial/a-better-way-to-learn-angularjs/&lt;br /&gt;
&lt;br /&gt;
Translating/internationalization:&lt;br /&gt;
http://plnkr.co/edit/Hw4iMz?p=info&lt;br /&gt;
http://mcgivery.com/internationalization-of-an-ionic-app-multilanguage-support/&lt;br /&gt;
&lt;br /&gt;
Angular directives list (curated)&lt;br /&gt;
http://angular-js.in/&lt;br /&gt;
&lt;br /&gt;
Angular 2:&lt;br /&gt;
http://eisenbergeffect.bluespire.com/all-about-angular-2-0/?utm_source=javascriptweekly&amp;amp;utm_medium=email&lt;br /&gt;
&lt;br /&gt;
[[Category: Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Automatic_Building_System&amp;diff=62156</id>
		<title>Moodle Mobile Automatic Building System</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Automatic_Building_System&amp;diff=62156"/>
		<updated>2022-04-29T07:14:16Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle App (Ionic 3)}}&lt;br /&gt;
&lt;br /&gt;
{{obsolete}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
An automatic building system helps developers a lot, they can focus on writing good code and forget all about building/compiling and all the boring stuff.&lt;br /&gt;
&lt;br /&gt;
Phonegap Build is an example of a powerful build system, but it has a big limitation for Moodle Mobile; it doesn&#039;t support some Phonegap plugins we use for the Android app.&lt;br /&gt;
&lt;br /&gt;
Implementing an automatic building system for Android is quite simple using 3rd party applications as described bellow:&lt;br /&gt;
&lt;br /&gt;
== Automatic Build System for Android ==&lt;br /&gt;
&lt;br /&gt;
=== Requirements ===&lt;br /&gt;
&lt;br /&gt;
* The MoodleMobile .apk file or your custom app .apk file. You need to perform an initial build on Android. See [[Moodle Mobile Building the app for Android]] for more info or obtain the current .apk file from Google Play using software like real apk leecher&lt;br /&gt;
&lt;br /&gt;
* android-apktool - http://code.google.com/p/android-apktool/&lt;br /&gt;
&lt;br /&gt;
* jarsigner - Part of the Java JDK&lt;br /&gt;
&lt;br /&gt;
=== Steps ===&lt;br /&gt;
&lt;br /&gt;
1 Extract your .apk file template in a directory (apktool d path/to/yourApp.apk path/to/output-folder)&lt;br /&gt;
&lt;br /&gt;
2 Download from moodlehq/moodlemobile repository the last files to -&amp;gt; path/to/output-folder/assets/www directory (where all the html, css, js files are stored)&lt;br /&gt;
&lt;br /&gt;
3 Re-build the .apk file (apktool b path/to/output-folder path/to/yourAppV2.apk )&lt;br /&gt;
&lt;br /&gt;
4 Re-sign the .apk file (jarsigner -verbose -keystore ~/.android/debug.keystore path/to/yourAppV2.apk androiddebugkey)&lt;br /&gt;
&lt;br /&gt;
That&#039;s all, as you can see once you haven an original .apk file, extract, add your modifications and re-package is very simple&lt;br /&gt;
&lt;br /&gt;
=== Automatic script ===&lt;br /&gt;
&lt;br /&gt;
Say you have:&lt;br /&gt;
&lt;br /&gt;
 1 /opt/moodlemobile/repository/...&lt;br /&gt;
 2 /opt/moodlemobile/apk-templates/myapp.apk&lt;br /&gt;
 3 /opt/moodlemobile/tmpdir&lt;br /&gt;
 4 /opt/moodlemobile/certificates&lt;br /&gt;
&lt;br /&gt;
1 A cloned git repository of your code&lt;br /&gt;
&lt;br /&gt;
2 Your original .apk file built with Android SDK&lt;br /&gt;
&lt;br /&gt;
3 An empty dir&lt;br /&gt;
&lt;br /&gt;
4 Certificate key store dir. The keystore is created using keytool, see http://docs.oracle.com/javase/1.3/docs/tooldocs/win32/keytool.html&lt;br /&gt;
Also see: http://developer.android.com/tools/building/building-cmdline.html and http://developer.android.com/tools/publishing/app-signing.html&lt;br /&gt;
&lt;br /&gt;
The script&lt;br /&gt;
&lt;br /&gt;
 cd /opt/moodlemobile&lt;br /&gt;
 rm -rf tmpdir/*&lt;br /&gt;
 cd repository&lt;br /&gt;
 git pull&lt;br /&gt;
 cd ../&lt;br /&gt;
 apktool d apk-templates/myapp.apk tmpdir/&lt;br /&gt;
 cp -r repository/* tmpdir/assets/www/&lt;br /&gt;
vi tmpdir/AndroidManifest.xml (debug must be false, bump version code and change version name)&lt;br /&gt;
 apktool b tmpdir mynewapp.apk&lt;br /&gt;
 jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore certificates/keystore mynewapk.apk mycertificatealiasinsidekeystore&lt;br /&gt;
 jarsigner -verbose -verify -keystore certificates/keystore mynewapk.apk ** Optional&lt;br /&gt;
 zipalign -v 4 mynewapk.apk mynewapkReadyForPlayStore.apk&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
https://github.com/jleyva/moodlemobile-scripts For sample code&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Building_the_app_for_Android&amp;diff=62155</id>
		<title>Moodle Mobile Building the app for Android</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Building_the_app_for_Android&amp;diff=62155"/>
		<updated>2022-04-29T07:14:12Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle App (Ionic 3)}}&lt;br /&gt;
Remember that you can also build the app using an external system called Phonegap Build, see [[Moodle_Mobile_Customization#Building]]&lt;br /&gt;
{{obsolete}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Installing the Android SDK ==&lt;br /&gt;
Building the app for Android is easy once you have successfully installed all the required software.&lt;br /&gt;
&lt;br /&gt;
Since we are using Phonegap as framework for creating the app, the documentation you have to follow is just in this link plus additional steps for installing plugins:&lt;br /&gt;
&amp;lt;s&amp;gt;docs.phonegap.com/en/guide_getting-started_android_index.md.html#Getting%20Started%20with%20Android&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Notice the following:&lt;br /&gt;
* You must read carefully the document and not use white spaces in some names and ids. (It&#039;s adviced in the document)&lt;br /&gt;
* You can optionally not use Eclipse and instead use adb command line or cordova script tools&lt;br /&gt;
* In the &amp;quot;4. Setup New Project&amp;quot; The package_name is the app id that your app will have in the Android Market (Play Store), so it must be unique. If you are going to develop for the official app you have to use com.moodle.moodlemobile, if you are going to develop a custom app, you will have to add your own unique id.&lt;br /&gt;
* If you want to connect a Device to your Eclipse and you are using Windows, you must install the Android USB Drivers, see http://developer.android.com/tools/extras/oem-usb.html#InstallingDriver&lt;br /&gt;
== Add the Mobile app HTML5 files ==&lt;br /&gt;
Go to your project folder, assets/www and add just there all the files from https://github.com/moodlehq/moodlemobile&lt;br /&gt;
== Installing additional Phonegap plugins ==&lt;br /&gt;
The Android app requires two additional Phonegap plugins to be installed:&lt;br /&gt;
=== WebIntent ===&lt;br /&gt;
For opening files according their mime type: https://github.com/phonegap/phonegap-plugins/tree/DEPRECATED/Android/WebIntent&lt;br /&gt;
&lt;br /&gt;
For installing the plugin follow this instructions: https://github.com/phonegap/phonegap-plugins/blob/DEPRECATED/Android/WebIntent/README.md#adding-the-plugin-to-your-project&lt;br /&gt;
== Build, compile, test your app ==&lt;br /&gt;
We suggest you to use Eclipse tools for testing, deploying the app&lt;br /&gt;
&lt;br /&gt;
In any case, you can always use the Cordova command line tools.&lt;br /&gt;
&lt;br /&gt;
The cli tools are in your project folder, directory cordova&lt;br /&gt;
== Signing your app ==&lt;br /&gt;
&#039;&#039;The Android system requires that all installed applications be digitally signed with a certificate whose private key is held by the application&#039;s developer. The Android system uses the certificate as a means of identifying the author of an application and establishing trust relationships between applications. The certificate is not used to control which applications the user can install. The certificate does not need to be signed by a certificate authority: it is perfectly allowable, and typical, for Android applications to use self-signed certificates.&#039;&#039; See: http://developer.android.com/tools/publishing/app-signing.html&lt;br /&gt;
&lt;br /&gt;
And also for Eclipse: http://developer.android.com/tools/publishing/app-signing.html#ExportWizard&lt;br /&gt;
== Uploading your custom app to the Google Play Store ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==  Build the official Moodle Mobile app on a Mac ==&lt;br /&gt;
# Install SDK&lt;br /&gt;
#*http://developer.android.com/sdk/index.html#download - it will install Eclipse / ADT plugin / Android SDK&lt;br /&gt;
# Add the SDK tools/platform-tools folders to your PATH&lt;br /&gt;
# Install NodeJs&lt;br /&gt;
#*http://nodejs.org/ &lt;br /&gt;
# Install cordova-cli&lt;br /&gt;
#* &#039;&#039;sudo npm install -g cordova&#039;&#039; - it will install cordova. Note that on my machine I have some warnings about the android bootstrap process being skipped but all the following steps seem to work ok anyway...&lt;br /&gt;
# Create a Android project&lt;br /&gt;
#*&#039;&#039;/usr/local/lib/node_modules/cordova/bin/cordova create ~/Apps/moodlemobile_android_cordova com.moodle.moodlemobile MoodleMobileBeta&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cd ~/Apps/moodlemobile_android_cordova&#039;&#039;&lt;br /&gt;
#* &#039;&#039;sudo /usr/local/lib/node_modules/cordova/bin/cordova platform add android&#039;&#039;&lt;br /&gt;
#* &#039;&#039;sudo /usr/local/lib/node_modules/cordova/bin/cordova build&#039;&#039; - not necessary, just check the build works.&lt;br /&gt;
# In Finder change the permission on ~/Apps/moodlemobile_android_cordova folder: Read + Write to everyone&lt;br /&gt;
# Open eclipse, add a new Android project from existing source pointing to /Users/jerome/Apps/moodlemobile_android_cordova/platforms/android. Test you can build the default Cordova app on your phone.&lt;br /&gt;
# Copy the Moodle mobile app into assests/www folder&lt;br /&gt;
#* &#039;&#039;git clone git://github.com/moodlehq/moodlemobile.git ./moodlemobile&#039;&#039;&lt;br /&gt;
#* &#039;&#039;mv platforms/android/assets/www/ platforms/android/assets/www_old/&#039;&#039;&lt;br /&gt;
#* &#039;&#039;mv ./moodlemobile/ platforms/android/assets/www/&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/android/assets/www_old/cordova.js platforms/android/assets/www/&#039;&#039;&lt;br /&gt;
# Add the cordova plugins&lt;br /&gt;
#* &#039;&#039;git clone git://github.com/alunny/ChildBrowser.git ./ChildBrowser&#039;&#039; - we are now going to install cordova compatible plugin with the plugin install command line.&lt;br /&gt;
#* &#039;&#039;sudo /usr/local/lib/node_modules/cordova/bin/cordova plugin add ChildBrowser/&#039;&#039;&lt;br /&gt;
# Add manually phonegap plugins&lt;br /&gt;
#* &#039;&#039;git clone git://github.com/phonegap/phonegap-plugins.git ./phonegap-plugins&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp phonegap-plugins/Android/WebIntent/webintent.js platforms/android/assets/www/&#039;&#039;&lt;br /&gt;
#* &#039;&#039;mkdir platforms/android/src/com/borismus&#039;&#039;&lt;br /&gt;
#* &#039;&#039;mkdir platforms/android/src/com/borismus/webintent&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp phonegap-plugins/Android/WebIntent/WebIntent.java platforms/android/src/com/borismus/webintent&#039;&#039;&lt;br /&gt;
#* In Eclipse, add a package to src folder. The value should be com.borismus.webintent&lt;br /&gt;
#* In your res/xml/config.xml file, add &#039;&#039;&amp;lt;plugin name=&amp;quot;WebIntent&amp;quot; value=&amp;quot;com.borismus.webintent.WebIntent&amp;quot; /&amp;gt;&#039;&#039; where you define the plugins.&lt;br /&gt;
# copy the icons&lt;br /&gt;
#* &#039;&#039;cp platforms/android/assets/www/img/icon/android/icon-36-ldpi.png platforms/android/res/drawable-ldpi/icon.png&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/android/assets/www/img/icon/android/icon-48-mdpi.png platforms/android/res/drawable-mdpi/icon.png&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/android/assets/www/img/icon/android/icon-72-hdpi.png platforms/android/res/drawable-hdpi/icon.png&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/android/assets/www/img/icon/android/icon-96-xhdpi.png platforms/android/res/drawable-xhdpi/icon.png&#039;&#039;&lt;br /&gt;
# Edit the AndroidManifest.xml&lt;br /&gt;
#* increment &#039;&#039;android:versionCode=&amp;quot;359&amp;quot; android:versionName=&amp;quot;1.2.0.2&amp;quot;&#039;&#039;&lt;br /&gt;
#* keep the following permissions: &#039;&#039;INTERNET, RECORD_AUDIO, RECORD_VIDEO, READ_CONTACTS, WRITE_CONTACTS, WRITE_EXTERNAL_STORAGE, ACCESS_NETWORK_STATE&#039;&#039;&lt;br /&gt;
#* &#039;&#039;android:debuggable=&amp;quot;false&amp;quot;&#039;&#039; - this reduce the apk size file from 7M to less than 1M!&lt;br /&gt;
# Edit &#039;&#039;res/values/strings.xml&#039;&#039; and change the app name for &amp;quot;Moodle mobile&amp;quot; - this app name appears on your Android phone dashboard. The Google Play Store app name comes from the Google Play publisher console.&lt;br /&gt;
# Clean up the app to reduce apk file size&lt;br /&gt;
#* &#039;&#039;rm -rf platforms/android/assets/www_old/&#039;&#039;&lt;br /&gt;
#* &#039;&#039;rm -rf platforms/android/assets/www/img/splash&#039;&#039;&lt;br /&gt;
#* &#039;&#039;rm -rf platforms/android/assets/www/img/icon&#039;&#039;&lt;br /&gt;
# In Eclispe, build and test the app.&lt;br /&gt;
# In Eclipse, File &amp;gt; Export the app with the Moodle HQ Google Play keystore. Then upload the APK to Google Play.&lt;br /&gt;
=== Warnings ===&lt;br /&gt;
* if you previously installed a different platform then you&#039;ll get an error when adding the cordova plugin. It&#039;s advised to install both platforms at the same time. Then add the cordova plugin(s), then copy the app content into www. Don&#039;t forget cordova.js and plugin js/files. Then you can manually install phonegap plugins.&lt;br /&gt;
= See also =&lt;br /&gt;
* [[Moodle Mobile Building the app for iOS]]&lt;br /&gt;
* [[Moodle Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Building_the_app_for_iOS&amp;diff=62154</id>
		<title>Moodle Mobile Building the app for iOS</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Building_the_app_for_iOS&amp;diff=62154"/>
		<updated>2022-04-29T07:14:10Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle App (Ionic 3)}}&lt;br /&gt;
&lt;br /&gt;
Remember that you can also build the app using an external system called Phonegap Build, see [[Moodle_Mobile_Customization#Building]]&lt;br /&gt;
&lt;br /&gt;
{{obsolete}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Mac OSX ==&lt;br /&gt;
this is a quick guide to build the official Moodle Mobile app on iOS. It should be pretty easy to do it for your own organization. &lt;br /&gt;
# Install xcode&lt;br /&gt;
# Install NodeJs&lt;br /&gt;
#*http://nodejs.org/ &lt;br /&gt;
# Install cordova-cli&lt;br /&gt;
#* &#039;&#039;sudo xcodebuild -license&#039;&#039; - agree to xcode license&lt;br /&gt;
#* &#039;&#039;sudo npm install -g cordova&#039;&#039; - it will install cordova. Note that on my machine I have some warnings about the ios bootstrap process being skipped but all the following steps seem to work ok anyway...&lt;br /&gt;
# Create a Xcode project&lt;br /&gt;
#* &#039;&#039;/usr/local/lib/node_modules/cordova/bin/cordova create ~/Apps/moodlemobile_android_cordova com.moodle.moodlemobile MoodleMobileBeta&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cd  ~/Apps/moodlemobile_android_cordova&#039;&#039;&lt;br /&gt;
#* &#039;&#039;sudo /usr/local/lib/node_modules/cordova/bin/cordova platform add ios&#039;&#039;&lt;br /&gt;
#* &#039;&#039;sudo /usr/local/lib/node_modules/cordova/bin/cordova build&#039;&#039; - not necessary, just check the build works.&lt;br /&gt;
# In Finder change the permission on ~/Apps/moodlemobile_android_cordova folder: Read + Write to everyone&lt;br /&gt;
# Add the cordova plugins&lt;br /&gt;
#* &#039;&#039;git clone git://github.com/alunny/ChildBrowser.git ./ChildBrowser&#039;&#039; - we are now going to install cordova compatible plugin with the plugin install command line.&lt;br /&gt;
#* &#039;&#039;sudo /usr/local/lib/node_modules/cordova/bin/cordova plugin add ChildBrowser/&#039;&#039;&lt;br /&gt;
# In Finder open &#039;&#039;/Users/jerome/Apps/moodlemobile_android_cordova/platforms/ios/MoodleMobileBeta.xcodeproj&#039;&#039;. Test you can build the default Cordova app on your phone.&lt;br /&gt;
# Copy the Moodle mobile app into www folder&lt;br /&gt;
#* &#039;&#039;git clone git://github.com/moodlehq/moodlemobile.git ./moodlemobile&#039;&#039;&lt;br /&gt;
#* &#039;&#039;mv platforms/ios/www/ platforms/ios/www_old/&#039;&#039;&lt;br /&gt;
#* &#039;&#039;mv ./moodlemobile/ platforms/ios/www/&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/ios/www_old/cordova.js platforms/ios/www/&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/ios/www_old/childbrowser.js platforms/ios/www/&#039;&#039;&lt;br /&gt;
#* &#039;&#039;mv platforms/ios/www_old/childbrowser platforms/ios/www/&#039;&#039;&lt;br /&gt;
# In Xcode, edit &#039;&#039;Resources &amp;gt; MoodleMobileBeta-Info.plist&#039;&#039;&lt;br /&gt;
#* Bundle display name: &#039;&#039;Moodle&#039;&#039; - &amp;quot;Moodle mobile&amp;quot; would be displayed &amp;quot;Moodl...obile&amp;quot;&lt;br /&gt;
#* Bundle version: &#039;&#039;1.2.0.3&#039;&#039;&lt;br /&gt;
# Add icons and splash&lt;br /&gt;
#* &#039;&#039;cp platforms/ios/www/img/icon/ios/icon-57.png platforms/ios/MoodleMobileBeta/Resources/icons/icon.png&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/ios/www/img/icon/ios/icon-57-2x.png platforms/ios/MoodleMobileBeta/Resources/icons/icon@2x.png&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/ios/www/img/icon/ios/icon-72.png platforms/ios/MoodleMobileBeta/Resources/icons/icon-72.png&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/ios/www/img/icon/ios/icon-72-2x.png platforms/ios/MoodleMobileBeta/Resources/icons/icon-72@2x.png&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/ios/www/img/splash/ios/* platforms/ios/MoodleMobileBeta/Resources/splash&#039;&#039;&lt;br /&gt;
# Go to Targets &amp;gt; Summary&lt;br /&gt;
#* check pre-rendered icon&lt;br /&gt;
#* enable all orientation&lt;br /&gt;
#* Deployment target: 4.3&lt;br /&gt;
#* Version: 1.203 Build: 1.2.0.3&lt;br /&gt;
#* Check that all icons and all splash-screens are there.&lt;br /&gt;
# Clean the app&lt;br /&gt;
#* &#039;&#039;rm -rf platforms/ios/www/img/splash&#039;&#039;&lt;br /&gt;
#* &#039;&#039;rm -rf platforms/ios/www/img/icon&#039;&#039;&lt;br /&gt;
# Build and publish&lt;br /&gt;
&lt;br /&gt;
=== Warnings ===&lt;br /&gt;
* If you previously installed a different platform then you&#039;ll get an error when adding the cordova plugin. It&#039;s advised to install both platforms at the same time. Then add the cordova plugin(s), then copy the app content into www. Don&#039;t forget cordova.js and plugin js/files. Then you can manually install phonegap plugins.&lt;br /&gt;
* The iOS version doesn&#039;t need WebIntent android plugin.&lt;br /&gt;
* You must add the childbrowser cordova plugin just after adding the platform. In Cordova 2.5, if you add the plugin after installing the app content, it creates a folder named childbrowser.js, and the plugin doesn&#039;t work.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Moodle Mobile Building the app for Android]]&lt;br /&gt;
* [[Moodle Mobile]]&lt;br /&gt;
* [http://photokandy.tumblr.com/post/46289610781/getting-started-with-cordova-cli Blog post on how to install cordova-cli]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_custom_apps&amp;diff=62153</id>
		<title>Moodle Mobile custom apps</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_custom_apps&amp;diff=62153"/>
		<updated>2022-04-29T07:14:08Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 2 (Ionic 1)}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
You will need a custom app if you want:&lt;br /&gt;
* Force the users to log in to your site URL&lt;br /&gt;
* Custom corporate image (name, app icon, theme, store entry)&lt;br /&gt;
* Add additional features or static pages&lt;br /&gt;
* Remove or change existing features&lt;br /&gt;
* Custom translation of the app&lt;br /&gt;
* Use your own notifications infrastructure&lt;br /&gt;
== Requirements ==&lt;br /&gt;
* Code editor (Sublime, WebStorm, Eclipse, Netbeans...)&lt;br /&gt;
* Github account (http://github.com/)&lt;br /&gt;
* Github desktop app (http://windows.github.com/)&lt;br /&gt;
* PhonegapBuild account&lt;br /&gt;
* Chromium browser as described here: [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
== Steps for creating your Custom App ==&lt;br /&gt;
=== Setting up your computer ===&lt;br /&gt;
Follow these guides:&lt;br /&gt;
* [[Setting up your development environment for Moodle Mobile 2]]&lt;br /&gt;
* [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
=== Fork the app ===&lt;br /&gt;
Use the Fork tool in:&lt;br /&gt;
https://github.com/moodlehq/moodleapp (You must be logged with your Github user!)&lt;br /&gt;
&lt;br /&gt;
[[File:forkCustomApp.png]]&lt;br /&gt;
=== Clone the repo in your desktop ===&lt;br /&gt;
You can use your favourite GIT client (or GitHub Desktop clients)&lt;br /&gt;
&lt;br /&gt;
[[File:clone.png]]&lt;br /&gt;
=== Create a new branch ===&lt;br /&gt;
Having the code in a separate branch in the forked repository will make very easy to maintain our customizations using git tools (merge, rebase)&lt;br /&gt;
&lt;br /&gt;
[[File:branch.png]]&lt;br /&gt;
=== Edit the app settings file ===&lt;br /&gt;
&#039;&#039;&#039;/config.xml&#039;&#039;&#039;&lt;br /&gt;
* Change the widget id value (instead com.moodle.moodlemobile your own id)&lt;br /&gt;
* Change the app name and description&lt;br /&gt;
&#039;&#039;&#039;/www/config.json&#039;&#039;&#039;&lt;br /&gt;
* Change versionname&lt;br /&gt;
* Add a pre-fixed URL: Adding a field like “siteurl”: “http://mysite.abc”&lt;br /&gt;
* Change gcmpn (if you are using your custom Google Push infrastructure)&lt;br /&gt;
* Change the appid (required for making Push notifications work)&lt;br /&gt;
* Remove demo sites (“demo_sites”: “”)&lt;br /&gt;
=== Edit another app files ===&lt;br /&gt;
&#039;&#039;&#039;/www/core/component/settings/lang/x.json (x means all the languages)&#039;&#039;&#039;&lt;br /&gt;
* Change the “appname” string to match your custom app name (Not necessary in latest version Moodle Mobile 3.1.3), you can change it in config.json&lt;br /&gt;
&#039;&#039;&#039;/www/errorreport.js&#039;&#039;&#039;&lt;br /&gt;
* Change: var reportUrl to point to your custom error reporting system (if you want to use your custom one)&lt;br /&gt;
* Same for appVersion&lt;br /&gt;
=== Replace the app icons, splashscreen ===&lt;br /&gt;
* Replace the resources/ icon.png and splash images&lt;br /&gt;
* Replace also the www/img/moodle.png with an image matching your logo&lt;br /&gt;
* &#039;&#039;&#039;In a console:&#039;&#039;&#039;&lt;br /&gt;
** cd mycustomappdir/&lt;br /&gt;
** ionic resources&lt;br /&gt;
This will create your custom icon and splash images in all the required sizes&lt;br /&gt;
&lt;br /&gt;
More information about Ionic’s image generation: http://ionicframework.com/docs/cli/icon-splashscreen.html&lt;br /&gt;
=== Add your custom styles ===&lt;br /&gt;
* Add your custom styles in scss/app.scss&lt;br /&gt;
* Sample theme available at: [[Moodle Mobile Themes]]&lt;br /&gt;
[[File:addCustomStyle.png]]&lt;br /&gt;
== Building your Custom App ==&lt;br /&gt;
There are two options for building the app:&lt;br /&gt;
* Ionic CLI&lt;br /&gt;
** Local building (require installation of Android and iOs SDK and emulators)&lt;br /&gt;
** See http://ionicframework.com/docs/cli/run.html&lt;br /&gt;
* Phonegap Build&lt;br /&gt;
** Remote building (cloud service)&lt;br /&gt;
** This is the recommended way because it doesn’t require to install all the SDK, emulators (or having to buy a Mac for developing)&lt;br /&gt;
=== Building with Phonegap Build ===&lt;br /&gt;
==== GitHub ====&lt;br /&gt;
* First of all, you need to create a new repository in GitHub. This is because Phonegap Build requires a different directory structure than Ionic CLI and we need a branch per platform.&lt;br /&gt;
* In your forked repository, run &#039;&#039;gulp&#039;&#039; so the build files are updated.&lt;br /&gt;
* Copy all the code inside the www/ in your forked repository directory to this new repo/branch.&lt;br /&gt;
* Also copy the /resources directory from the ionic project to a new /resources directory in the phonegap build repository&lt;br /&gt;
* You also have to place the /resources/icon.png and /resources/splash.png you used to create your resource files in the root folder in the phonegap build repository&lt;br /&gt;
* Use this repository as an example: &amp;lt;s&amp;gt;github.com/moodlehq/moodlemobile-phonegapbuild/tree/learn-moodle&amp;lt;/s&amp;gt;&lt;br /&gt;
* In the Phonegap Build repo root folder you need to add a special config.xml file (different to the Ionic one), use [https://raw.githubusercontent.com/moodlehq/moodlemobile-phonegapbuild/learn-moodle/config.xml this file] as a template. Please, replace there the app name, versions, etc.&lt;br /&gt;
* Then, in the Phonegap build repo you need two create two additional branches based on master: android and ios&lt;br /&gt;
* In the android and ios branch you need to apply these changes:&lt;br /&gt;
Disable debugging in the app:&lt;br /&gt;
&lt;br /&gt;
[[file:disableDebug.png]]&lt;br /&gt;
&lt;br /&gt;
Add android or ios base class (so the app use native style widgets):&lt;br /&gt;
&lt;br /&gt;
[[file:addBaseClass.png]]&lt;br /&gt;
* The rest of the commits can be safely omitted:&lt;br /&gt;
** Add reporting library&lt;br /&gt;
** Add official tag&lt;br /&gt;
==== Phonegap ====&lt;br /&gt;
* Create an account in &#039;&#039;PhoneGap&#039;&#039;.&lt;br /&gt;
* For iOS you need to create certificates/provisioning files.&lt;br /&gt;
* For Android, you can download and install the .apk for testing, for production follow the previous guide.&lt;br /&gt;
* In order to build, add your repository https link and also the branch in the Phonegap Build settings (you will have to do two different builds: one for the android branch and another one for the ios branch)&lt;br /&gt;
* In order to update your app for phonegap build you have to (this will apply all the specific android/ios commits to your master branch):&lt;br /&gt;
** Upload new versions of the app to the www/ directory and then execute the commands:&lt;br /&gt;
** git rebase master android&lt;br /&gt;
** git push -f&lt;br /&gt;
** git rebase master ios&lt;br /&gt;
** git push -f&lt;br /&gt;
=== Remove existing features ===&lt;br /&gt;
* You can only remove add-ons.&lt;br /&gt;
* In order to disable an add-on you need to delete the folder.&lt;br /&gt;
* Add-ons are located in the www/addons folder.&lt;br /&gt;
* In case you want to remove a particular functionality of an add-on you will need to edit the files inside the addon. Please notice that removing a state is not enough for a feature to be disabled, you also need to remove all the links to that state to prevent errors.&lt;br /&gt;
* Then run gulp or ionic serve in the project root folder so the build files are created.&lt;br /&gt;
=== Change the app strings ===&lt;br /&gt;
* Language strings are located in:&lt;br /&gt;
** www/core/lang folder (a different file for each language)&lt;br /&gt;
** www/core/components/COMPONENT_NAME/lang folder&lt;br /&gt;
** www/addons/ADDON_NAME/lang folder&lt;br /&gt;
* Language files are .json files, you can edit it using a text editor. Please, use jsonlint.com to validate that the new language file is correct.&lt;br /&gt;
* Then run &#039;&#039;&#039;gulp&#039;&#039;&#039; or &#039;&#039;&#039;ionic serve&#039;&#039;&#039; in the project root folder so the build files are created&lt;br /&gt;
=== Change the initial screen (add site) ===&lt;br /&gt;
* To make the app use a fixed site URL you need to add a new setting in the config.json file: “siteurl”: “http://yourmoodle.com”&lt;br /&gt;
* Then run &#039;&#039;&#039;gulp&#039;&#039;&#039; or &#039;&#039;&#039;ionic serve&#039;&#039;&#039; in the project root folder so the build files are created.&lt;br /&gt;
You can see an example from our Learn Moodle app (that is a custom version of Moodle Mobile) here: https://github.com/jleyva/moodleapp/blob/learn-moodle/www/config.json&lt;br /&gt;
=== Keep your custom version up2date ===&lt;br /&gt;
* Sync your fork: https://help.github.com/articles/syncing-a-fork/&lt;br /&gt;
* Rebase your changes (and resolve conflicts):&lt;br /&gt;
** git rebase master yourcustombranch&lt;br /&gt;
* Upgrade your version number&lt;br /&gt;
* Commit your changes&lt;br /&gt;
* Apply the changes in your Phonegap Build specific repository&lt;br /&gt;
* Rebuild with Phonegap build for both android and ios branches&lt;br /&gt;
* Publish in the store&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Development_Process&amp;diff=62152</id>
		<title>Moodle Mobile Development Process</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Development_Process&amp;diff=62152"/>
		<updated>2022-04-29T07:14:06Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Warning|This page is no longer in use. The information contained on the page should NOT be seen as relevant or reliable.}}&lt;br /&gt;
&lt;br /&gt;
== Versions ==&lt;br /&gt;
&lt;br /&gt;
Moodle Mobile version numbers consist of three numbers separated by a dot, for example 1.3.2 or 1.4.4. The first two numbers, like 1.3, represent the major version. The third number distinguishes minor versions within the same major version. &lt;br /&gt;
&lt;br /&gt;
=== Major and minor versions ===&lt;br /&gt;
&lt;br /&gt;
* Major version includes new features and improvements.&lt;br /&gt;
&lt;br /&gt;
* Minor version includes only bug fixes.&lt;br /&gt;
&lt;br /&gt;
The current release cycle is one version per month (in the first two weeks of every month)&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
[[Moodle Mobile Release Notes]]&lt;br /&gt;
&lt;br /&gt;
[[Category: Mobile]] [[Category: Obsolete]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Testing_with_Phonegap_Developer_app&amp;diff=62151</id>
		<title>Moodle Mobile Testing with Phonegap Developer app</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Testing_with_Phonegap_Developer_app&amp;diff=62151"/>
		<updated>2022-04-29T07:14:02Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 1}}&lt;br /&gt;
 &#039;&#039;Phonegap Build&#039;&#039; service was discontinued October, 1 2020.&lt;br /&gt;
The &#039;&#039;Phonegap Developer app&#039;&#039; is a tool that allow developing locally and see the changes instantly on your mobile device.&lt;br /&gt;
&lt;br /&gt;
It&#039;s not a tool that can be used for developing and testing all the features of the Moodle Mobile app since it doesn&#039;t support all the plugins used by the app and also, the FileTransfer plugin are not fully supported.&lt;br /&gt;
&lt;br /&gt;
It doesn&#039;t support also connection to different sites using XHR (Ajax) request using POST or HEAD methods.&lt;br /&gt;
&lt;br /&gt;
The app is suitable for testing mainly CSS / Design issues in different devices (iOs, Android and WindowsPhone 8)&lt;br /&gt;
&lt;br /&gt;
If you want to test the Moodle Mobile app in order to polish design issues, please follow this instructions:&lt;br /&gt;
* Install the Phonegap Developer app in your iOs, Android or WindowsPhone 8 device&lt;br /&gt;
* Install node.js http://nodejs.org/&lt;br /&gt;
* In a terminal/console: sudo npm install -g phonegap&lt;br /&gt;
* Terminal/console: cd directory_where_to_create_the_project&lt;br /&gt;
* Terminal/console: phonegap create moodlemobile&lt;br /&gt;
* Terminal/console: cd moodlemobile&lt;br /&gt;
* Delete the contents of the www/ folder&lt;br /&gt;
* Copy the Moodle Mobile app files into the www/ folder&lt;br /&gt;
* In a terminal, cd to the project folder and then: phonegap serve (This will launch a local server, you can stop the server doing a ctrl+c)&lt;br /&gt;
* Open the app in a device, and copy the server Address that the previous command displayed&lt;br /&gt;
* In the Moodle Mobile app Site URL field enter: mmtest (It&#039;s a &amp;quot;fake&amp;quot; test site, remember that you can&#039;t connect to real sites using the Phonegap Developer app)&lt;br /&gt;
Remember that every time you want to use the Phonegap Developer app, you need to open a terminal/console, cd to your phonegap project folder, and run the command:&lt;br /&gt;
 phonegap serve&lt;br /&gt;
If you have problems testing with the app, try to close the app in your Mobile device and stop the Phonegap server (ctrl + c in the terminal)&lt;br /&gt;
[[Category: Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_Moodle_Mobile_2_(Ionic_1)&amp;diff=62150</id>
		<title>Setting up your development environment for Moodle Mobile 2 (Ionic 1)</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_Moodle_Mobile_2_(Ionic_1)&amp;diff=62150"/>
		<updated>2022-04-29T07:13:59Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 2 (Ionic 1)}}&lt;br /&gt;
&lt;br /&gt;
IMPORTANT: This documentation will be obsolete in May 2018, please [https://moodle.org/mod/forum/discuss.php?d=360412 read this announcement].&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The majority of your development work will be done using the browser. You will likely begin to use an emulator once you need to simulate a real mobile device.&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install a browser for development&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
We recommend Chromium browser (Google Chrome open source version) https://download-chromium.appspot.com/&lt;br /&gt;
Please, read [[Moodle_Mobile_development_using_Chrome_or_Chromium]] for more information&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install git&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
https://git-scm.com/book/en/v2/Getting-Started-Installing-Git&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install Node.js&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
http://nodejs.org&lt;br /&gt;
&lt;br /&gt;
For Mac users we recommend to install NodeJS via Macports.&lt;br /&gt;
&lt;br /&gt;
Node 6.9.1 has been verified to work fine with the app. If you&#039;re having problems with later versions you might want to check if it works with 6.9.1.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;note&amp;quot;: if you also need later versions of node for other projects, use Node Version Manager aka &amp;quot;nvm&amp;quot; [https://github.com/creationix/nvm here]. Linux and OSX is supported, and the site has pointers to windows alternatives. Simply type:&lt;br /&gt;
   nvm install 6.9.1&lt;br /&gt;
   nvm use 6.9.1&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install ionic:&#039;&#039;&#039; &lt;br /&gt;
 npm cache clean&lt;br /&gt;
 npm install -g cordova@6.5.0 ionic@2.2.3      # (If it throws an EACCESS error, run it again with sudo)&lt;br /&gt;
&lt;br /&gt;
Please notice that Ionic 2.2.3 is being used. The CLI for Ionic 3 breaks our building system, there is an open issue to fix this: [https://tracker.moodle.org/browse/MOBILE-2112 MOBILE-2112].&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install the npm required packages&#039;&#039;&#039;&lt;br /&gt;
 sudo npm install -g bower                     # (This will install bower in a folder that should be in the PATH)&lt;br /&gt;
 sudo npm install -g gulp                      # (This will install gulp in a folder that should be in the PATH)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Push notifications for Mac&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Phonegap plugin push 1.9.0 requires CocoaPods to work. The installation steps can be found in https://cocoapods.org/&lt;br /&gt;
&lt;br /&gt;
Please note that for compiling the app in Mac you need to open the .xcworkspace file, more information here: MOBILE-1970&lt;br /&gt;
&lt;br /&gt;
== Clone the app base code ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Clone the code base into a local directory in your computer.&#039;&#039;&#039;&lt;br /&gt;
 git clone https://github.com/moodlehq/moodlemobile2.git moodlemobiledirectory&lt;br /&gt;
 cd moodlemobiledirectory&lt;br /&gt;
&lt;br /&gt;
== Automatically setup the environment ==&lt;br /&gt;
&lt;br /&gt;
Please, note that if you are creating a custom app with a custom URL scheme, you should edit the /package.json file and specify there your custom URL_SCHEME (replacing the existing value) and your [https://github.com/phonegap/phonegap-plugin-push/blob/master/docs/INSTALLATION.md GCMPN SENDER_ID].&lt;br /&gt;
&lt;br /&gt;
The following command must be run in the project&#039;s root folder:&lt;br /&gt;
&lt;br /&gt;
 npm run setup&lt;br /&gt;
&lt;br /&gt;
This command will throw some warnings, since Ionic 1 requires some libraries that are deprecated. Just ignore all the npm WARN messages.&lt;br /&gt;
&lt;br /&gt;
If this instruction is successful you can jump to the section [[#Open_the_app_in_the_browser|Open the app in the browser]]. If it fails, please check the next section.&lt;br /&gt;
&lt;br /&gt;
== Manually setup the environment ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install the npm plugins dependencies&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
 npm install (This will install all the dependencies listed in package.json)&lt;br /&gt;
&lt;br /&gt;
This command will throw some warnings, since Ionic 1 requires some libraries that are deprecated. Just ignore all the npm WARN messages.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Add the iOS and Android platforms and install the required Cordova plugins&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Please, note that if you are creating a custom app with a custom URL scheme, you should edit the /config.xml file and specify there your custom URL_SCHEME (replacing the existing value) and your [https://github.com/phonegap/phonegap-plugin-push/blob/master/docs/INSTALLATION.md GCMPN SENDER_ID].&lt;br /&gt;
&lt;br /&gt;
Run the following command to install the platforms and all the required Cordova plugins:&lt;br /&gt;
 cordova prepare&lt;br /&gt;
&lt;br /&gt;
Cordova Android 6.1.2 and Cordova iOS 4.3.1 have been verified to work fine with the app. If you&#039;re having problems with later versions you might want to check if it works with these versions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install javascript libraries&#039;&#039;&#039;&lt;br /&gt;
 bower install (this will install all the libraries listed in bower.json)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Run gulp’s default tasks (in order to create the build files)&#039;&#039;&#039;&lt;br /&gt;
 gulp&lt;br /&gt;
&lt;br /&gt;
== Open the app in the browser ==&lt;br /&gt;
First start Chromium via the command line using the custom parameters as is mentioned here: [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
&lt;br /&gt;
and then, start the Ionic server:&lt;br /&gt;
&lt;br /&gt;
 ionic serve --browser chromium&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to open any browser you should run:&lt;br /&gt;
&lt;br /&gt;
 ionic serve -b&lt;br /&gt;
&lt;br /&gt;
== Editor == &lt;br /&gt;
&lt;br /&gt;
There are several plugins for AngularJS and Ionic development available for common editors.&lt;br /&gt;
&lt;br /&gt;
Sublime 3 has plugins for [https://github.com/SublimeLinter/SublimeLinter-jscs jscs] (coding style) and [https://github.com/SublimeLinter/SublimeLinter-jshint jshint] (linter)&lt;br /&gt;
&lt;br /&gt;
Configuration files for jscs and jshint can be downloaded from https://github.com/angular/angular.js (.jscs.json and .jshint*)&lt;br /&gt;
&lt;br /&gt;
== Updating ionic and cordova ==&lt;br /&gt;
&lt;br /&gt;
 sudo npm update -g cordova&lt;br /&gt;
 sudo npm update -g ionic&lt;br /&gt;
&lt;br /&gt;
Update project platforms:&lt;br /&gt;
&lt;br /&gt;
 ionic platform remove android&lt;br /&gt;
 ionic platform remove ios&lt;br /&gt;
 ionic platform add android&lt;br /&gt;
 ionic platform add ios&lt;br /&gt;
&lt;br /&gt;
== Updating plugins ==&lt;br /&gt;
&lt;br /&gt;
 cordova plugin remove your_plugin_id&lt;br /&gt;
 cordova plugin add your_plugin_id&lt;br /&gt;
&lt;br /&gt;
== Building for Android and iOS ==&lt;br /&gt;
&lt;br /&gt;
Please see this guide to be able to build for Android and iOS using the command line:&lt;br /&gt;
&lt;br /&gt;
http://cordova.apache.org/docs/en/5.0.0/guide_platforms_index.md.html#Platform%20Guides&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Common errors when building&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* com.android.dex.DexException: Multiple dex files define XXX&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add these code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations {&lt;br /&gt;
      all*.exclude group: &#039;com.android.support&#039;, module: &#039;support-v4&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
* Could not resolve all dependencies for configuration &#039;:_debugCompile&#039;.&lt;br /&gt;
Open the Android SDK Manager and make sure you have installed: Android Support Repository, Android Support Library, Google Play Services and Google Repository.&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Error: libsass bindings not found. Try reinstalling node-sass? ===&lt;br /&gt;
&lt;br /&gt;
Please read: http://fettblog.eu/gulp-and-node4-first-aid/, alternatively you must be sure that you installed Node v0.12&lt;br /&gt;
&lt;br /&gt;
=== node-gyp\src\win_delay_load_hook.c(34): error C2373: &#039;__pfnDliNotifyHook2&#039;: redefinition; different type modifiers ===&lt;br /&gt;
&lt;br /&gt;
Try updating npm to the latest version using: &lt;br /&gt;
&lt;br /&gt;
  npm install -g npm@latest&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
http://ionicframework.com/docs/cli/&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Upgrading_Airnotifier&amp;diff=62149</id>
		<title>Upgrading Airnotifier</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Upgrading_Airnotifier&amp;diff=62149"/>
		<updated>2022-04-29T07:12:50Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Warning|This page is no longer in use. The information contained on the page should NOT be seen as relevant or reliable.}}&lt;br /&gt;
&lt;br /&gt;
Steps to upgrade&lt;br /&gt;
&lt;br /&gt;
* Stop the server, killing the process&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 ps aux | grep airnotifier&lt;br /&gt;
 kill -9 PROCESS_ID&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Backup the MongoDB databases&lt;br /&gt;
*# Backup AirNotifier settings database &amp;lt;br/&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;mongodump -h localhost --port 27017 -d airnotifier -o /var/airnotifier/&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
*# Backup each application data&amp;lt;br/&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;mongodump -h localhost --port 27017 -d commoodlemoodlemobile -o /var/airnotifier/;&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;mongodump -h localhost --port 27017 -d commoodlemoodlemobiletest -o /var/airnotifier/&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Pull latest version from git&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
 cd /opt/airnotifier&lt;br /&gt;
 git pull&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Check that your current version has a version number in the options collection (AirNotifier settings database) (Older AirNotifier versions doesn&#039;t have it), use the shell:&lt;br /&gt;
 mongo&lt;br /&gt;
 use airnotifier&lt;br /&gt;
 db.options.find()&lt;br /&gt;
&lt;br /&gt;
* If there is not a value option, create a new one:&lt;br /&gt;
 db.options.insert({name: &amp;quot;version&amp;quot;, value: 20140101})&lt;br /&gt;
&lt;br /&gt;
* Run the upgrade.py script&lt;br /&gt;
 cd /opt/airnotifier&lt;br /&gt;
 python upgrade.py&lt;br /&gt;
&lt;br /&gt;
* Start the server again&lt;br /&gt;
 ./startserver&lt;br /&gt;
or&lt;br /&gt;
 sudo -u airnotifier python airnotifier.py &amp;gt;&amp;gt; /var/airnotifier/console_log 2&amp;gt;&amp;gt; /var/airnotifier/error_log &amp;amp;&lt;br /&gt;
&lt;br /&gt;
[[Category: Mobile]] [[Category: Obsolete]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Developing_a_plugin_tutorial_part_2&amp;diff=62148</id>
		<title>Moodle Mobile 1 Developing a plugin tutorial part 2</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Developing_a_plugin_tutorial_part_2&amp;diff=62148"/>
		<updated>2022-04-29T07:09:26Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 1}}&lt;br /&gt;
&lt;br /&gt;
== Introduction ==&lt;br /&gt;
&lt;br /&gt;
In the second part of this tutorial we are going to add new WebServices inside our custommm local plugin.&lt;br /&gt;
&lt;br /&gt;
The new WebServices will let us to add a new &amp;quot;forums&amp;quot; plugin to or custom MoodleMobile app.&lt;br /&gt;
&lt;br /&gt;
== Plugin spec ==&lt;br /&gt;
&lt;br /&gt;
We want to develop a &amp;quot;forums&amp;quot; plugin that will display all the forums an user it&#039;s available to see inside a course.&lt;br /&gt;
We will be able to read discussions and post (only read, not write)&lt;br /&gt;
&lt;br /&gt;
A screenshot of the desired result:&lt;br /&gt;
&lt;br /&gt;
[[File:forums.png]]&lt;br /&gt;
&lt;br /&gt;
== Steps ==&lt;br /&gt;
&lt;br /&gt;
=== Add the new WebServices in the local plugin ===&lt;br /&gt;
&lt;br /&gt;
You cand find here the plugin with the forum WebServices added, https://github.com/cvaconsulting/moodle-local_custommm&lt;br /&gt;
&lt;br /&gt;
When adding new WebServices to a plugin, don&#039;t forget to update the services file and also bump the version number.&lt;br /&gt;
&lt;br /&gt;
Update the plugin in your installation as an usual plugin upgrade process.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Add the new WebServices to your custommm service ===&lt;br /&gt;
&lt;br /&gt;
In your Moodle installation, you need to go to Admin -&amp;gt; Plugins -&amp;gt; WebServices -&amp;gt; Services, edit the custommm service for adding the new forums WebServices&lt;br /&gt;
&lt;br /&gt;
=== Set up your MoodleMobile client ===&lt;br /&gt;
&lt;br /&gt;
Edit the config.json file, and add &amp;quot;forums&amp;quot; in the plugins setting&lt;br /&gt;
&lt;br /&gt;
 &amp;quot;plugins&amp;quot; : [&amp;quot;notifications&amp;quot;, &amp;quot;upload&amp;quot;, &amp;quot;contents&amp;quot;, &amp;quot;participants&amp;quot;, &amp;quot;addcontact&amp;quot;, &amp;quot;addnote&amp;quot;, &amp;quot;sendmessage&amp;quot;, &amp;quot;groups&amp;quot;, &amp;quot;forums&amp;quot;],&lt;br /&gt;
&lt;br /&gt;
=== Develop the MoodleMobile plugin ===&lt;br /&gt;
&lt;br /&gt;
The full source code can be found here:&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-forums&lt;br /&gt;
&lt;br /&gt;
[[Category: Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Plugins_Development&amp;diff=62147</id>
		<title>Moodle Mobile 1 Plugins Development</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Plugins_Development&amp;diff=62147"/>
		<updated>2022-04-29T07:09:23Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 1}}&lt;br /&gt;
&lt;br /&gt;
=== Overview ===&lt;br /&gt;
&lt;br /&gt;
Plugins allow developers to extend the app functionalities.&lt;br /&gt;
A plugin is a subdirectory that implements a set of required functionalities.&lt;br /&gt;
&lt;br /&gt;
IMPORTANT: Plugins are not automatically loaded, you must indicate the plugins to be loaded in the config.json file (plugins option).&lt;br /&gt;
&lt;br /&gt;
IMPORTANT: Read carefully this document ([[Moodle Mobile 1 Customization]]) before you start developing!&lt;br /&gt;
&lt;br /&gt;
You can view a step by step guide of how to create a plugin in this presentation [http://es.slideshare.net/juanleyva/creating-a-custom-moodle-mobile-app-moodle-moot-spain-2014 Creating a custom Moodle Mobile app - MoodleMoot Spain 2014]&lt;br /&gt;
&lt;br /&gt;
=== Types of plugins ===&lt;br /&gt;
&lt;br /&gt;
* General: Interactions over the global app, such as the Notifications, Upload, Help and Web&lt;br /&gt;
* Course: Interactions over a course, such as course contents or participants&lt;br /&gt;
* User: Interactions over an user, such as send a message, add as a contact, write a private note&lt;br /&gt;
* Settings: Additional settings for the app&lt;br /&gt;
&lt;br /&gt;
=== Structure of a plugin ===&lt;br /&gt;
&lt;br /&gt;
We are going to use this plugin: https://github.com/moodlehq/moodlemobile/tree/master/plugins/participants as an example:&lt;br /&gt;
&lt;br /&gt;
Plugins have always the same structure (no matter the type of plugin), the app uses a Register App where you declare a Plugin and then register it to the App.&lt;br /&gt;
&lt;br /&gt;
A plugin must be a directory under the plugins/ dir containing:&lt;br /&gt;
&lt;br /&gt;
* A main.js file, where the plugin is declared&lt;br /&gt;
* Templates html files (if used)&lt;br /&gt;
&lt;br /&gt;
====Defining a plugin====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var templates = [&lt;br /&gt;
    &amp;quot;root/lib/text!root/plugins/participants/participants.html&amp;quot;,&lt;br /&gt;
    &amp;quot;root/lib/text!root/plugins/participants/participant.html&amp;quot;&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
define(templates,function (participantsTpl, participantTpl) {&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For loading plugins we use the RequireJS library, a plugin is a module that we must define using the lines above:&lt;br /&gt;
&lt;br /&gt;
We define a new module that depends on the html files (templates) listed above, once loaded, the contents of the HTML files will be available in the participantsTpl and participantTpl variables.&lt;br /&gt;
&lt;br /&gt;
Notice that the app doesn&#039;t automatically load the plugins, you need to edit the config.json file for indicating the plugins to be loaded&lt;br /&gt;
&lt;br /&gt;
 &amp;quot;plugins&amp;quot; : [&amp;quot;notifications&amp;quot;, &amp;quot;upload&amp;quot;, &amp;quot;contents&amp;quot;, &amp;quot;participants&amp;quot;, &amp;quot;addcontact&amp;quot;, &amp;quot;addnote&amp;quot;, &amp;quot;sendmessage&amp;quot;, &amp;quot;yourpluginname&amp;quot;],&lt;br /&gt;
&lt;br /&gt;
====Global settings of the plugin====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
 settings: {&lt;br /&gt;
            name: &amp;quot;participants&amp;quot;,&lt;br /&gt;
            type: &amp;quot;course&amp;quot;,&lt;br /&gt;
            menuURL: &amp;quot;#participants/&amp;quot;,&lt;br /&gt;
            lang: {&lt;br /&gt;
                component: &amp;quot;moodle&amp;quot;&lt;br /&gt;
            }&lt;br /&gt;
        },&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The name of the plugin (must be the same that the directory name)&lt;br /&gt;
&lt;br /&gt;
The type of plugin (general, course, user, settings)&lt;br /&gt;
&lt;br /&gt;
The main link to the plugin in the App&lt;br /&gt;
&lt;br /&gt;
The language file to use, here we are using moodle because we are using the main language file, for plugins, you should use the Moodle franken-style plugin name where the lang file is located in your Moodle installation i.e: &amp;quot;local_mycustomplugin&amp;quot;&lt;br /&gt;
&lt;br /&gt;
This means that you need to put your language files in a local plugin (called mycustomplugin) in your Moodle installation, this local plugin should be the same where you are going to add your custom Web Services&lt;br /&gt;
&lt;br /&gt;
Example settings for custom plugins:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
 settings: {&lt;br /&gt;
            name: &amp;quot;mycustomplugin&amp;quot;,&lt;br /&gt;
            type: &amp;quot;general&amp;quot;,&lt;br /&gt;
            menuURL: &amp;quot;#custom/&amp;quot;,&lt;br /&gt;
            lang: {&lt;br /&gt;
                component: &amp;quot;local_mycustomplugin&amp;quot;,&lt;br /&gt;
                strings: {&lt;br /&gt;
                 &amp;quot;stringid1&amp;quot;: &amp;quot;string contents&amp;quot;,&lt;br /&gt;
                 &amp;quot;stringid2&amp;quot;: &amp;quot;string contents 2&amp;quot;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        },&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Notice that the lang.strings property is needed because until we sync to the remote Moodle installation for downloading the language pack, we are going to need this temporal strings.&lt;br /&gt;
&lt;br /&gt;
You can also can add the strings in a json file and uses the same method that for loading templates (add the lang json file as a dependency and then assign to the lang.strings property the variable returned by the define function, see above) &lt;br /&gt;
&lt;br /&gt;
====Storage====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
        storage: {&lt;br /&gt;
            participant: {type: &amp;quot;model&amp;quot;},&lt;br /&gt;
            participants: {type: &amp;quot;collection&amp;quot;, model: &amp;quot;participant&amp;quot;}&lt;br /&gt;
        },&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we declare the &amp;quot;tables&amp;quot; and their &amp;quot;structure&amp;quot;&lt;br /&gt;
&lt;br /&gt;
If you are not familiar with Models, you must think that participants is a table that contains the participant&#039;s records.&lt;br /&gt;
&lt;br /&gt;
Notice that we are not indicating the &amp;quot;fields&amp;quot; of the &amp;quot;tables&amp;quot; this is not necessary.&lt;br /&gt;
&lt;br /&gt;
====Routes====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
        routes: [&lt;br /&gt;
            [&amp;quot;participants/:courseId&amp;quot;, &amp;quot;participants&amp;quot;, &amp;quot;showParticipants&amp;quot;],&lt;br /&gt;
            [&amp;quot;participant/:courseId/:userId&amp;quot;, &amp;quot;participants&amp;quot;, &amp;quot;showParticipant&amp;quot;],&lt;br /&gt;
        ],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For avoid binding DOM elements to functions, we use a Route navigation model, any time an user clicks on a button or link, the hash part of the URL is changed and the function linked to the hash part is triggered.&lt;br /&gt;
So, if you want to trigger the function ShowParticipant, you must create a button or link that points to &amp;quot;#participant/courseid/userid&amp;quot;, when the user click on the link the function showParticipant(courseid, userid) is triggered&lt;br /&gt;
One of the advantages of using roues, is that we preserve the browser history so the &amp;quot;back&amp;quot; button on an Android device will work as expected without any extra code.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Sync, aka cron or periodic tasks&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
        sync: {&lt;br /&gt;
            handler: MM.plugins.myplugin.functionName,&lt;br /&gt;
            time: 60&lt;br /&gt;
        },&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can hook to the sync/cron/periodic tasks core subsystem, just create a property called Sync with two sub-properties:&lt;br /&gt;
&lt;br /&gt;
* handler: The function that is going to be executed&lt;br /&gt;
* time: The execution interval&lt;br /&gt;
&lt;br /&gt;
The sync subsystem is used for synchronize language strings and also for sending operations performed when offline.&lt;br /&gt;
&lt;br /&gt;
====Functions====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        showParticipants: function(courseId) {&lt;br /&gt;
            MM.panels.showLoading(&#039;center&#039;);&lt;br /&gt;
            &lt;br /&gt;
            if (MM.deviceType == &amp;quot;tablet&amp;quot;) {&lt;br /&gt;
                MM.panels.showLoading(&#039;right&#039;);&lt;br /&gt;
            }&lt;br /&gt;
    &lt;br /&gt;
            var data = {&lt;br /&gt;
                &amp;quot;courseid&amp;quot; : courseId&lt;br /&gt;
            };&lt;br /&gt;
            &lt;br /&gt;
            MM.moodleWSCall(&#039;moodle_user_get_users_by_courseid&#039;, data, function(users) {&lt;br /&gt;
                var tpl = {users: users, deviceType: MM.deviceType, courseId: courseId};&lt;br /&gt;
                var html = MM.tpl.render(MM.plugins.participants.templates.participants.html, tpl);&lt;br /&gt;
                MM.panels.show(&#039;center&#039;, html);&lt;br /&gt;
                // Load the first user&lt;br /&gt;
                if (MM.deviceType == &amp;quot;tablet&amp;quot; &amp;amp;&amp;amp; users.length &amp;gt; 0) {&lt;br /&gt;
                    MM.plugins.participants.showParticipant(courseId, users.shift().id);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        },&lt;br /&gt;
&lt;br /&gt;
        showParticipant: function(courseId, userId) {&lt;br /&gt;
            var data = {&lt;br /&gt;
                &amp;quot;userlist[0][userid]&amp;quot;: userId,&lt;br /&gt;
                &amp;quot;userlist[0][courseid]&amp;quot;: courseId&lt;br /&gt;
            }&lt;br /&gt;
            MM.moodleWSCall(&#039;moodle_user_get_course_participants_by_id&#039;, data, function(users) {&lt;br /&gt;
                // Load the active user plugins.&lt;br /&gt;
                &lt;br /&gt;
                var userPlugins = [];&lt;br /&gt;
                for (var el in MM.plugins) {&lt;br /&gt;
                    var plugin = MM.plugins[el];&lt;br /&gt;
                    if (plugin.settings.type == &amp;quot;user&amp;quot;) {&lt;br /&gt;
                        userPlugins.push(plugin.settings);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                &lt;br /&gt;
                var tpl = {&amp;quot;user&amp;quot;: users.shift(), &amp;quot;plugins&amp;quot;: userPlugins, &amp;quot;courseid&amp;quot;: courseId};&lt;br /&gt;
                var html = MM.tpl.render(MM.plugins.participants.templates.participant.html, tpl);&lt;br /&gt;
                MM.panels.show(&#039;right&#039;, html);&lt;br /&gt;
            });&lt;br /&gt;
        },&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the main plugin functions, as you can see we create a function for any single route defined.&lt;br /&gt;
&lt;br /&gt;
As you can see, you don&#039;t need much code:&lt;br /&gt;
&lt;br /&gt;
The showParticipants function does the following:&lt;br /&gt;
&lt;br /&gt;
* Shows a loading icon in the center panel&lt;br /&gt;
&lt;br /&gt;
* If we are using a tablet, an additional loading icon is displayed in the right panel&lt;br /&gt;
&lt;br /&gt;
* Then we call a Moodle Web service, using the MM.moodleWSCall function, indicating the name of the WS, the parameters, an a callback function.&lt;br /&gt;
&lt;br /&gt;
* We can add an extra parameter, indicating that this function perform write actions in the server, see: https://github.com/moodlehq/moodlemobile/blob/master/plugins/addnote/main.js#L36&lt;br /&gt;
&lt;br /&gt;
* When the Web service returns info, the next step is to render a Template using the app template function and then display the template in the center panel.&lt;br /&gt;
&lt;br /&gt;
Notice that for referencing the template, we use the MM global object: MM.plugins.participants.templates.participants.html (MM . registered plugins . name of the plugin . property . template name . contents of the template&lt;br /&gt;
&lt;br /&gt;
* If we are using a tablet, we load in the right panel the first participant calling the showParticipant function&lt;br /&gt;
&lt;br /&gt;
Notice that for referencing the function, we use the MM global object: M.plugins.participants.showParticipant &lt;br /&gt;
&lt;br /&gt;
====Templates====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
templates: {&lt;br /&gt;
            &amp;quot;participant&amp;quot;: {&lt;br /&gt;
                model: &amp;quot;participant&amp;quot;,&lt;br /&gt;
                html: participantTpl&lt;br /&gt;
            },&lt;br /&gt;
            &amp;quot;participants&amp;quot;: {&lt;br /&gt;
                html: participantsTpl&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here we declare the templates we are going to use for further references.&lt;br /&gt;
&lt;br /&gt;
Notice that the attribute html cointains the HTML template files contents (as mentioned above).&lt;br /&gt;
&lt;br /&gt;
====Register the plugin====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
 MM.registerPlugin(plugin);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
With this single line we register the plugin in the global Namespace MM (the main library of the app)&lt;br /&gt;
&lt;br /&gt;
Notice that registering a plugin is NOT mandatory, so you can have plugins doesn&#039;t registered in the app. See &amp;quot;Use cases for plugins&amp;quot; section for more info.&lt;br /&gt;
&lt;br /&gt;
== Use cases for plugins ==&lt;br /&gt;
&lt;br /&gt;
=== Override API functions ===&lt;br /&gt;
&lt;br /&gt;
Since plugins are loaded before the app starts and after the global MM object is loaded, you can overwrite at any time global functions.&lt;br /&gt;
&lt;br /&gt;
So before the Plugin registring you can do thinks like&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
 MM.log = function(info) {&lt;br /&gt;
  // Here goes the code for my custom log function that overrides the default functionality&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
 MM.registerPlugin(plugin);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Load a custom CSS stylesheet ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
 $(&#039;head&#039;).append(&#039;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;plugins/myplugin/mycss.css&amp;quot; type=&amp;quot;text/css&amp;quot; /&amp;gt;&#039;);&lt;br /&gt;
&lt;br /&gt;
 MM.registerPlugin(plugin);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Change the main layout of the app ===&lt;br /&gt;
&lt;br /&gt;
In this case we change the Add Site screen.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
var templates = [&lt;br /&gt;
    &amp;quot;root/lib/text!root/plugins/myplugin/tpl.html&amp;quot;,&lt;br /&gt;
    &amp;quot;root/lib/text!root/plugins/myplugin/addsite.html&amp;quot;&lt;br /&gt;
];&lt;br /&gt;
 &lt;br /&gt;
define(templates,function (baseTpl, myAddSiteCustomTpl) {&lt;br /&gt;
&lt;br /&gt;
[..]&lt;br /&gt;
&lt;br /&gt;
 $(&#039;#add-site_template&#039;).html(myAddSiteCustomTpl);&lt;br /&gt;
&lt;br /&gt;
 MM.registerPlugin(plugin);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Load extra base languages ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
var templates = [&lt;br /&gt;
    &amp;quot;root/lib/text!root/plugins/myplugin/lang1.json&amp;quot;,&lt;br /&gt;
    &amp;quot;root/lib/text!root/plugins/myplugin/lang2.json&amp;quot;&lt;br /&gt;
];&lt;br /&gt;
 &lt;br /&gt;
define(templates,function (lang1, lang2) {&lt;br /&gt;
&lt;br /&gt;
[..]&lt;br /&gt;
&lt;br /&gt;
 MM.loadLang(&#039;core&#039;, &#039;es&#039;, JSON.parse(lang1.json));&lt;br /&gt;
 MM.loadLang(&#039;core&#039;, &#039;cat&#039;, JSON.parse(lang2.json));&lt;br /&gt;
&lt;br /&gt;
 MM.registerPlugin(plugin);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In all the cases, you can omit to register the Plugin if it doesn&#039;t fit in a standard one.&lt;br /&gt;
&lt;br /&gt;
== Moodle Mobile (MM) API ==&lt;br /&gt;
&lt;br /&gt;
Most of the MM APIs are wrappers for other libraries like Backbone.&lt;br /&gt;
&lt;br /&gt;
=== DB/Storage functions ===&lt;br /&gt;
&lt;br /&gt;
====Getting an element from storage by id====&lt;br /&gt;
&lt;br /&gt;
 MM.db.get(collection, id);&lt;br /&gt;
&lt;br /&gt;
====Getting elements from storage using conditions====&lt;br /&gt;
&lt;br /&gt;
 MM.db.where(collection, {name: value});&lt;br /&gt;
&lt;br /&gt;
Sample code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var notificationsFilter = MM.db.where(&amp;quot;notifications&amp;quot;, {siteid: MM.config.current_site.id});&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Adding a Model to a Collection (inserting an element into a table)====&lt;br /&gt;
&lt;br /&gt;
 MM.db.insert(collection, {id: xx, name: yy, value: zz})&lt;br /&gt;
&lt;br /&gt;
 MM.db.insert(collection, {name: yy, value: zz}) (This will create a random Unique id)&lt;br /&gt;
&lt;br /&gt;
====Deleting a Modelfrom a Collection by id====&lt;br /&gt;
&lt;br /&gt;
 MM.db.delete(collection, modelId);&lt;br /&gt;
&lt;br /&gt;
====Iterate over all the Models of a Collection====&lt;br /&gt;
&lt;br /&gt;
 MM.db.each(collection, function(model) { // stuff here});&lt;br /&gt;
&lt;br /&gt;
Sample code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
            MM.db.each(&amp;quot;sync&amp;quot;, function(sync){&lt;br /&gt;
                sync = sync.toJSON();&lt;br /&gt;
                MM.log(&amp;quot;Execugin WS sync operation:&amp;quot; + JSON.stringify(sync.syncData) + &amp;quot;url:&amp;quot; + sync.url);&lt;br /&gt;
                MM.moodleWSCall(sync.data.wsfunction, sync.data, function(d) { &lt;br /&gt;
                    MM.log(&amp;quot;Execugin WS sync operation FINISHED:&amp;quot; + sync.data.wsfunction);&lt;br /&gt;
                    MM.db.delete(&amp;quot;sync&amp;quot;, sync.id);&lt;br /&gt;
                }, {cache: 0});&lt;br /&gt;
            });&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Internationalization functions ===&lt;br /&gt;
&lt;br /&gt;
====Getting a translated string====&lt;br /&gt;
&lt;br /&gt;
 MM.lang.s(&amp;quot;string_id&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
Sample code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
MM.lang.s(&amp;quot;therearenotnotificationsyet&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Templating functions ===&lt;br /&gt;
&lt;br /&gt;
====Render a template====&lt;br /&gt;
&lt;br /&gt;
 MM.tpl.render(html, elements);&lt;br /&gt;
&lt;br /&gt;
Sample code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var tpl = {users: users, deviceType: MM.deviceType, courseId: courseId};&lt;br /&gt;
var html = MM.tpl.render(MM.plugins.participants.templates.participants.html, tpl);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Template:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html4strict&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;section class=&amp;quot;users-index-list&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;form class=&amp;quot;search&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;input type=&amp;quot;search&amp;quot; results=&amp;quot;5&amp;quot; placeholder=&amp;quot;Search...&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/form&amp;gt;&lt;br /&gt;
        &amp;lt;ul class=&amp;quot;nav nav-v&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;% _.each(users, function(user) { %&amp;gt;&lt;br /&gt;
            &amp;lt;li class=&amp;quot;nav-item&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;a href=&amp;quot;#participant/&amp;lt;%= courseId %&amp;gt;/&amp;lt;%= user.id %&amp;gt;&amp;quot; class=&amp;quot;media&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;div class=&amp;quot;img&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;img width=&amp;quot;35&amp;quot; src=&amp;quot;&amp;lt;%= MM.fixPluginfile(user.profileimageurlsmall) %&amp;gt;&amp;quot; alt=&amp;quot;img&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;/div&amp;gt;&lt;br /&gt;
                    &amp;lt;div class=&amp;quot;bd&amp;quot;&amp;gt;&lt;br /&gt;
                      &amp;lt;h3&amp;gt;&amp;lt;%= user.fullname %&amp;gt;&amp;lt;/h3&amp;gt;&lt;br /&gt;
                    &amp;lt;/div&amp;gt;&lt;br /&gt;
                &amp;lt;/a&amp;gt;&lt;br /&gt;
            &amp;lt;/li&amp;gt;&lt;br /&gt;
            &amp;lt;% }); %&amp;gt;&lt;br /&gt;
        &amp;lt;/ul&amp;gt;&lt;br /&gt;
    &amp;lt;/section&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Message popups/dialogs===&lt;br /&gt;
&lt;br /&gt;
 MM.popMessage(text, options);&lt;br /&gt;
&lt;br /&gt;
 MM.popErrorMessage(text, options);&lt;br /&gt;
&lt;br /&gt;
=== Device ===&lt;br /&gt;
&lt;br /&gt;
 MM.deviceType (returns phone or tablet)&lt;br /&gt;
&lt;br /&gt;
 MM.deviceConnected() (returns true or false if the device has Internet access)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
For a tutorial about developing plugins for Moodle Mobile see: [[Moodle Mobile 1 Developing a plugin tutorial]]&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
[http://es.slideshare.net/juanleyva/creating-a-custom-moodle-mobile-app-moodle-moot-spain-2014 Creating a custom Moodle Mobile app - MoodleMoot Spain 2014]&lt;br /&gt;
JsDoc Reference http://moodlehq.github.com/moodlemobile/&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_simulator&amp;diff=62146</id>
		<title>Moodle Mobile 1 simulator</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_simulator&amp;diff=62146"/>
		<updated>2022-04-29T07:09:21Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
The Moodle Mobile 1 Simulator (MMS) https://github.com/jleyva/moodlemobile-simulator  is a tool to make easier developing for Moodle Mobile, it includes options for simulate Push notifications, watch and filter the app log, browse the &amp;quot;sandboxed&amp;quot; Chromium file system etc...&lt;br /&gt;
{{obsolete}}&lt;br /&gt;
{{Moodle Mobile 1}}&lt;br /&gt;
&lt;br /&gt;
[[{{ns:file}}:moodlemobilesimulator.png|thumb|300px]]&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
* It can emulate different devices/platforms viewport size (iPhone, iPad, Nexus4...)&lt;br /&gt;
* You can switch between different versions of the app and between different Test Sites&lt;br /&gt;
* It support emulation of &amp;quot;Offline&amp;quot; mode&lt;br /&gt;
* It cam simulate injection of Mobile themes (Mobile CSS URL)&lt;br /&gt;
* Rotate device&lt;br /&gt;
* Disable support of the local_mobile plugin for sites using that plugin&lt;br /&gt;
* Live CSS injection and template modifications for quick testing&lt;br /&gt;
* Emulation of Push notifications with configurable payload&lt;br /&gt;
* Support the Phonegap FileSystem and FileTransfer API so you can download/upload and store files in the Chromium filesystem&lt;br /&gt;
* Support additional app options like taking photos (using the webcam) an uploading audio recording&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&lt;br /&gt;
* First of all you need to configure Chrome or Chromium as described here [[Moodle_Mobile_development_using_Chrome_or_Chromium]]&lt;br /&gt;
&lt;br /&gt;
* Download the project from github: https://github.com/jleyva/moodlemobile-simulator&lt;br /&gt;
&lt;br /&gt;
* Open the index.html in the Chrome/Chromium browser (you don&#039;t need to install the simulator in a local server, you can just open the file in the browser doing a File -&amp;gt; Open)&lt;br /&gt;
&lt;br /&gt;
* In Settings Apps, add a new App pointing to the directory where your HTML code is placed&lt;br /&gt;
&lt;br /&gt;
* You can also add Apps and Sites in the config.json file (elements added here can be deleted using the GUI, you should delete it directly from the JSON file)&lt;br /&gt;
&lt;br /&gt;
* Go to Launch, select your App and device (optionally a Site) and &amp;quot;Launch app&amp;quot; Once logged in a site, it will require you to allow use the local file system storage capabilities&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Themes&amp;diff=62145</id>
		<title>Moodle Mobile 1 Themes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Themes&amp;diff=62145"/>
		<updated>2022-04-29T07:09:19Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 1}}&lt;br /&gt;
&lt;br /&gt;
The Mobile app can retrieve your custom styles from your Moodle site. Since is an HTML5 app, you can apply safely CSS3 styles.&lt;br /&gt;
&lt;br /&gt;
In your Moodle installation go to Plugins / Web services / Mobile and enter in the mobilecssurl field a valid URL pointing to a CSS file containing your custom styles (theme). &lt;br /&gt;
&lt;br /&gt;
The CSS should be placed inside your Moodle installation (in your custom theme or inside a local plugin)&lt;br /&gt;
&lt;br /&gt;
Once the user is logged in the app, there is a periodical process that retrieves your remote CSS files for applying your custom styles into the app. &lt;br /&gt;
&lt;br /&gt;
Notice that on the first time a user opens the app, he will see the default &amp;quot;orange&amp;quot; style. Your custom styles will be applied once the user has added a site in the app.&lt;br /&gt;
&lt;br /&gt;
Notice also that styles comes from a site, there is no way for changing the Add site / Manage account styles.&lt;br /&gt;
&lt;br /&gt;
== Elements and properties that can be styled ==&lt;br /&gt;
&lt;br /&gt;
The images below show the elements and properties that can be styled.&lt;br /&gt;
&lt;br /&gt;
[[File:styleable_elements.png]]&lt;br /&gt;
&lt;br /&gt;
The page title that is visible only on mobile at the moment.&lt;br /&gt;
&lt;br /&gt;
[[File:styleable_elements_mobile.png]]&lt;br /&gt;
&lt;br /&gt;
== How to modify the app icons ==&lt;br /&gt;
&lt;br /&gt;
There are several ways for modifying the app icons&lt;br /&gt;
&lt;br /&gt;
All the parent elements of img tags containing app icons has a &amp;quot;app-ico&amp;quot; class applied, so it&#039;s very easy to modify icons using styles&lt;br /&gt;
&lt;br /&gt;
=== Use CSS filters === &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
.app-ico img {&lt;br /&gt;
 -webkit-filter: invert(100%); &lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will make all the icons black&lt;br /&gt;
&lt;br /&gt;
More information about filters: http://www.html5rocks.com/en/tutorials/filters/understanding-css/&lt;br /&gt;
&lt;br /&gt;
=== Replace images using CSS ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
img[src*=&amp;quot;notifications/icon.png&amp;quot;] {&lt;br /&gt;
    content: url(dataURI);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Notice that for allowing the app works offline, images hosted in remotes servers should not be referenced. Instead, data URIs should be used. Note also that this makes the CSS file bigger.&lt;br /&gt;
&lt;br /&gt;
More information about data URIs http://css-tricks.com/data-uris/&lt;br /&gt;
&lt;br /&gt;
== Testing ==&lt;br /&gt;
&lt;br /&gt;
You can use the [[Moodle Mobile simulator]] for testing your CSS file, it includes an option for simulating the Mobile CSS URL Moodle feature.&lt;br /&gt;
&lt;br /&gt;
Also, you can use Safari or Google Chrome developer tools for debugging and changing the CSS styles in live:&lt;br /&gt;
&lt;br /&gt;
https://developer.chrome.com/devtools/docs/remote-debugging&lt;br /&gt;
&lt;br /&gt;
http://phonegap-tips.com/articles/debugging-ios-phonegap-apps-with-safaris-web-inspector.html&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Example of a  custom theme (CSS file) ==&lt;br /&gt;
&lt;br /&gt;
Notice that the data URI for the image is shorten, you can view the complete example here: https://gist.githubusercontent.com/jleyva/0821122c3461925b45a7/raw/425821513072daf7ce5a16874f3ea21ffe92325e/moodlemobile-sample_theme.css&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
body {&lt;br /&gt;
  color: #666666;				/*default*/&lt;br /&gt;
  background: #4bcaff;&lt;br /&gt;
}&lt;br /&gt;
a {&lt;br /&gt;
  color: #0088cc;				/*default*/&lt;br /&gt;
}&lt;br /&gt;
h1 {&lt;br /&gt;
  color: #4bcaff;&lt;br /&gt;
}&lt;br /&gt;
h2 {&lt;br /&gt;
  color: #ffffff;&lt;br /&gt;
  text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2);	/*default*/&lt;br /&gt;
  background-color: #7a8a94;&lt;br /&gt;
}&lt;br /&gt;
.nav .nav-item a {&lt;br /&gt;
  color: #666666;				/*default*/&lt;br /&gt;
}&lt;br /&gt;
#manage-accounts .account-details {&lt;br /&gt;
  color: #666666;				/*default*/&lt;br /&gt;
}&lt;br /&gt;
.header-wrapper {&lt;br /&gt;
  box-shadow: 10px 0 20px rgba(0, 0, 0, 0.3);	/*default*/&lt;br /&gt;
}&lt;br /&gt;
.header-main {&lt;br /&gt;
  color: #4b565d;&lt;br /&gt;
  background: #4bcaff;&lt;br /&gt;
}&lt;br /&gt;
#page-title {&lt;br /&gt;
  text-shadow: none;&lt;br /&gt;
}&lt;br /&gt;
#panel-right,&lt;br /&gt;
#panel-center {&lt;br /&gt;
  background: #ffffff;				/*default*/&lt;br /&gt;
}&lt;br /&gt;
.user-menu {&lt;br /&gt;
  background: #4bcaff;&lt;br /&gt;
}&lt;br /&gt;
.user-menu header {&lt;br /&gt;
  background: #4b565d url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAU***);&lt;br /&gt;
  border-bottom: 5px solid #ffffff;&lt;br /&gt;
  box-shadow: 10px 0 20px rgba(0, 0, 0, 0.3);&lt;br /&gt;
}&lt;br /&gt;
.user-menu header h1 {&lt;br /&gt;
  color: #ffffff;				/*default*/&lt;br /&gt;
  text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2);	/*default*/&lt;br /&gt;
}&lt;br /&gt;
.user-menu h2 {&lt;br /&gt;
  color: #4b565d;&lt;br /&gt;
  text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2);	/*default*/&lt;br /&gt;
  background: #ffffff;&lt;br /&gt;
  box-shadow: none;&lt;br /&gt;
}&lt;br /&gt;
.user-menu .nav-item &amp;gt; a {&lt;br /&gt;
  color: #4b565d;&lt;br /&gt;
  text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2);	/*default*/&lt;br /&gt;
}&lt;br /&gt;
.user-menu .nav-item .plugin-ico,&lt;br /&gt;
.user-menu .nav-item .course-ico {&lt;br /&gt;
  background-color: #4b565d;&lt;br /&gt;
}&lt;br /&gt;
.grades .section-name {&lt;br /&gt;
  color: #ffffff;&lt;br /&gt;
  text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.2);	/*default*/&lt;br /&gt;
  background-color: #7a8a94;&lt;br /&gt;
  border-bottom: 1px solid #ffffff;		/*default*/&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
http://caniuse.com/ Support tables for CSS3 styles (including Mobile browsers)&lt;br /&gt;
&lt;br /&gt;
[[Category: Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)&amp;diff=62144</id>
		<title>Moodle Mobile 2 (Ionic 1)</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)&amp;diff=62144"/>
		<updated>2022-04-29T07:09:16Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;For user documentation see &#039;&#039;&#039;[[:en:Moodle Mobile|Moodle Mobile]]&#039;&#039;&#039;.&amp;lt;/p&amp;gt;&lt;br /&gt;
{{Moodle Mobile 2 (Ionic 1)}}&lt;br /&gt;
&lt;br /&gt;
IMPORTANT: This documentation will be obsolete in May 2018, please [https://moodle.org/mod/forum/discuss.php?d=360412 read this announcement].&lt;br /&gt;
&lt;br /&gt;
== General Overview ==&lt;br /&gt;
&lt;br /&gt;
Moodle Mobile 2 (MM2) is the official mobile app for Moodle. MM2 is an HTML5 app that uses the Ionic hybrid apps framework. Former Moodle Mobile versions (1.x) used jQuery and Backbone (see [[Moodle Mobile 1]] for documentation about the former app).&lt;br /&gt;
&lt;br /&gt;
Basically, the app is a Web Services client that uses REST as protocol to obtain and send information to your Moodle installation/s.&lt;br /&gt;
&lt;br /&gt;
Interaction with the phone and compiling/packaging is done using the Cordova framework (also commonly known as Phonegap).&lt;br /&gt;
&lt;br /&gt;
MM1 plugins are not going to work in MM2.&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
&lt;br /&gt;
* Moodle 2.4&lt;br /&gt;
* Mobile services enabled (Administration -&amp;gt; Plugins -&amp;gt; Web Services -&amp;gt; Mobile)&lt;br /&gt;
&lt;br /&gt;
Future versions will require the last minor versions if Web Services are not back ported.&lt;br /&gt;
&lt;br /&gt;
== Architecture ==&lt;br /&gt;
&lt;br /&gt;
MM2 is built with ionic framework, ionic is a full stack mobile hybrid apps that uses AngularJS framework.&lt;br /&gt;
&lt;br /&gt;
AngularJS is a structural framework for dynamic web apps. It lets you use HTML as your template language and lets you extend HTML&#039;s syntax to express your application&#039;s components clearly and succinctly.&lt;br /&gt;
&lt;br /&gt;
AngularJS is quite popular, you may find lot of sites with updated documentation and free courses, the same applies for Ionic.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Ionic / AngularJS ===&lt;br /&gt;
&lt;br /&gt;
The app architecture is the typical for an AngularJS app. We use controllers, factories, directives, providers, etc..&lt;br /&gt;
&lt;br /&gt;
The app is designed in a modular way, most of the functionalities are AngularJs modules that implements plugins (in a similar way to Moodle plugins).&lt;br /&gt;
&lt;br /&gt;
There are a set of core services for handling authentication, configuration, plugin management, cache, etc… and specific modules for tasks like internationalization.&lt;br /&gt;
&lt;br /&gt;
Most of the services are implemented as factories.&lt;br /&gt;
&lt;br /&gt;
The app uses AngularJs logging to create an application log (only in developer mode).&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== App structure and naming conventions ===&lt;br /&gt;
&lt;br /&gt;
The app is structured in two parts: core and addons. &lt;br /&gt;
&lt;br /&gt;
In core we have the code that the app needs to be able to work (basic version). There are services, components, directives, filters, etc. &lt;br /&gt;
&lt;br /&gt;
The components inside core are similar to addons, but the app needs them to work: login, sidemenu, courses, etc. Each component needs to define a main.js file to initialize it and to define the module. &lt;br /&gt;
&lt;br /&gt;
Naming conventions for core:&lt;br /&gt;
* The module name for components needs to be mm.core.componentname and it should be defined in the component’s main.js. The services, filters and directives that don’t belong to a component need to use the module mm.core (defined in www/core/main.js).  &lt;br /&gt;
* All the services names need to start with $mm, followed by the service name in camel-case. For example, $mmSitesManager.&lt;br /&gt;
* The filters and directives use camel-case starting with mm. For example, mmFormatText.&lt;br /&gt;
* The controllers names need to start with mm (without dollar), followed by the controller name in camel-case. The controller name needs to contain Ctrl to easily identify it as a controller. For example, mmCourseListCtrl.&lt;br /&gt;
&lt;br /&gt;
In addons we have the official MM2 addons. Each addon can have services, controllers, templates, directives, filters, styles and lang files. The addon needs  to specify a main.js file to initialize the addon and to register itself into one or more delegates (this determines where will the plugin be shown). &lt;br /&gt;
&lt;br /&gt;
Naming conventions for addons:&lt;br /&gt;
* The module name for addons needs to be mm.addons.addonname and it should be defined in the addon&#039;s main.js. &lt;br /&gt;
* All the services names inside an addon need to start with $mma, followed by the addon name and the service name in camel-case. For example, $mmaMessagesHandlers.&lt;br /&gt;
* The filters and directives inside an addon use camel-case starting with mma followed by the addon name and the directive/filter name. For example, mmaMessagesFormat.&lt;br /&gt;
* The controllers names need to start with mma (without dollar), followed by the addon name and the controller name in camel-case. The controller name needs to contain Ctrl to easily identify it as a controller. For example, mmaForumListCtrl.&lt;br /&gt;
&lt;br /&gt;
Naming conventions for subaddons:&lt;br /&gt;
&lt;br /&gt;
Subaddons match Moodle subplugins or subsystems (like question behaviours, question types or quiz access rules).&lt;br /&gt;
* The module name for subaddons needs to be mm.addons.addonname_subaddonname and it should be defined in the addon&#039;s main.js. &lt;br /&gt;
* The rest of the conventions are the same that for addons but adding the subaddon name to the addon name. See www/addons/qtype or www/addons/qbehaviour for examples&lt;br /&gt;
&lt;br /&gt;
=== Internationalization ===&lt;br /&gt;
&lt;br /&gt;
The app is translated to several languages, we use a module called angular-translate that supports lazy-loading of languages and pluralization. All the languages files are shipped with the app except the ones for remote addons.&lt;br /&gt;
&lt;br /&gt;
To load the language files we use angular-translate-loader-partial, a loader developed by angular-translate authors that lets you load “parts” of the translation table. In MM 2.0 we’ll only have a single language file under www/build/lang, but with this loader we should be able to add remote plugins lang files too when they are implemented.&lt;br /&gt;
&lt;br /&gt;
Each plugin/component needs to define its own lang files if they need any. These files need to be placed inside a lang folder and must be named &#039;&#039;languagecode.json&#039;&#039;, where &#039;&#039;languagecode&#039;&#039; is the ISO code of the language (i.e. &#039;&#039;en.json&#039;&#039;). The structure of those files must be the following:&lt;br /&gt;
&lt;br /&gt;
 {&lt;br /&gt;
    “langkey””: “The string to be shown”&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
All the lang files are merged into a single lang file per language inside www/build/lang. In this merge process, the strings are prefixed to prevent naming collisions. The prefixes are:&lt;br /&gt;
&lt;br /&gt;
* Core strings (www/core/lang): mm.core.*&lt;br /&gt;
* Component strings (www/core/components/mycomponent/lang): mm.mycomponent.*&lt;br /&gt;
* Addons strings (www/addons/myaddon/lang): mma.myaddon.*&lt;br /&gt;
&lt;br /&gt;
For example, if the example lang file from above was inside the folder www/core/courses/component/lang, the merged file would have an entry like this:&lt;br /&gt;
&lt;br /&gt;
 “mm.courses.langkey”: “The string to be shown”&lt;br /&gt;
&lt;br /&gt;
To use the language string, the prefixed version must be used. For example, to use the langkey string from the courses component we need to do this:&lt;br /&gt;
&lt;br /&gt;
 $translate(&#039;mm.courses.langkey’) - Returns a Promise&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;{{ &#039;mm.courses.langkey’ | translate}}&amp;lt;/nowiki&amp;gt; - Used in a view, shows the translated string.&lt;br /&gt;
&lt;br /&gt;
Angular-translate is initialized inside www/core/lib/lang.js, where we set English as the fallback language and we load the current language based on the user app settings and the device locale. Notice that locales like “en-uk” or “es-mx” will be treated as “en” or “es” if that translation is not found.&lt;br /&gt;
&lt;br /&gt;
=== Storage ===&lt;br /&gt;
&lt;br /&gt;
The app needs to store some information in the mobile device: the current site information, the list of recorded sites, a cache of the REST calls, etc…&lt;br /&gt;
&lt;br /&gt;
For app storage we use the ydn-db javascript library, it supports storage in IndexedDB, WebSQL and localStorage implementing polyfills, promises, automatic storage system detection, etc... &lt;br /&gt;
&lt;br /&gt;
We have two levels of databases: the app database (1 DB for the whole app) and the site databases (1 DB per site). All the data that is site-related is stored in the site database, so it’s easier to retrieve and delete (when a site is deleted).&lt;br /&gt;
&lt;br /&gt;
The app DB is defined inside www/core/lib/app.js. This file implements a provider with the functions $mmAppProvider.registerStore and $mmAppProvider.registerStores to add new stores to the app DB.&lt;br /&gt;
&lt;br /&gt;
The site DB is defined inside www/core/lib/sitesfactory.js. This factory manages the creation and delete of the sites DB, creating a DB per site.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IMPORTANT&#039;&#039;&#039;: Modifying the schema of an already existing store deletes all its data in WebSQL Storage (in IndexedDB the data is kept). If a store schema needs to be modified, the data should be manually migrated to the new store.&lt;br /&gt;
&lt;br /&gt;
=== Styles ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;./scss/app.scss&#039;&#039;&#039;&lt;br /&gt;
The main SCSS file which a themer should be using to customise the look and feel of the app. That will also be the file we start from to make colour palettes. As core developers we will use this app to override the core ionic variables, as well as modifying existing ionic styles.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;./www/core/scss&#039;&#039;&#039;&lt;br /&gt;
This directory holds the styling components we want to offer to our themers. The variables associated with those components should be held in the same files and flagged as !default to allow themers to override them. We should not override those variables in ./scss/app.scss because default values should be provided by us and work with our core app style.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;./www/core/components/X/scss &amp;amp; ./www/core/addons/X/scss&#039;&#039;&#039;&lt;br /&gt;
Those directories will contain styles that are specific and should not be re-use by other components/addons. The same goes with the variables from those addons, they should not be re-used from another component/addon.&lt;br /&gt;
&lt;br /&gt;
To prevent naming collisions between SCSS files, the classes and IDs should be prefixed. The naming conventions for those prefixes are similar to the rest of Moodle Mobile 2 prefixes:&lt;br /&gt;
&lt;br /&gt;
* Core styles should be prefixed with mm-. For example, .mm-split-pane-menu.&lt;br /&gt;
* Components styles should be prefixed with mm-componentname-. For example, .mm-login-myclass.&lt;br /&gt;
* Addons styles should be prefixed with mma-addonname-. For example, .mma-grades-table.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Cordova ===&lt;br /&gt;
We use Cordova to access the mobile device APIs. We try to use only core Cordova plugins but sometimes that’s impossible.&lt;br /&gt;
If we have to use additional plugins (like Webintents, local notifications, custom URL schemes) we use only plugins well-maintained, generally those listed here: http://plugins.telerik.com/&lt;br /&gt;
&lt;br /&gt;
For integrating Cordova plugins we use ngCordova; ngCordova is a collection of AngularJS extensions on top of the Cordova API that make it easy to build, test, and deploy Cordova mobile apps with AngularJS. The main feature of ngCordova is that instead callbacks it uses promises, so it integrate very well with AngularJS.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Testing ===&lt;br /&gt;
Tests are written using Jasmine and run using Karma.&lt;br /&gt;
Karma is a JavaScript command line tool that can be used to spawn a web server which loads your application&#039;s source code and executes your tests.&lt;br /&gt;
&lt;br /&gt;
Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Addons ===&lt;br /&gt;
&lt;br /&gt;
The app comes with a predefined set of addons: messages, course contents, notifications, etc. All these addons are inside the www/addons folder. The structure of the files inside an addon is explained in the section App structure and naming conventions.&lt;br /&gt;
&lt;br /&gt;
An addon can be shown in several places. The app has one delegate per each place an addon can be shown, so each addon can register itself to any set of delegates.&lt;br /&gt;
&lt;br /&gt;
The delegates to display data in certain places are:&lt;br /&gt;
&lt;br /&gt;
* $mmSideMenuDelegate: The addons registered in here will be shown in the app’s side menu.&lt;br /&gt;
* $mmCoursesDelegate: The addons registered in here will be shown under each course in the course list.&lt;br /&gt;
* $mmCourseDelegate: The addons registered in here need to define which module they handle (forum, assign, book, ...). When a user opens a certain module, the addon registered for that module type will be used. There can only be one addon per module type.&lt;br /&gt;
* $mmUserDelegate: The addons registered in here will be shown in the user profile page.&lt;br /&gt;
* $mmSettingsDelegate: The addons registered in here will be shown in App Settings.&lt;br /&gt;
* $mmPluginFileDelegate: The addons registered in here will be able to replace plugin file urls to remove revision number and improve performance when downloading them.&lt;br /&gt;
&lt;br /&gt;
Delegates related to Moodle subsystems or subplugins that in the app will be implemented as subaddons:&lt;br /&gt;
&lt;br /&gt;
* $mmaModQuizAccessRulesDelegate: You can use this service to register your own access rules handlers to be used in a quiz. Addons should be placed in www/addons/mod_quiz/accessrules/&lt;br /&gt;
* $mmQuestionBehaviourDelegateProvider: You can use this provider to register your own question behaviour handlers to be used in a quiz or other places.  Addons should be placed in www/addons/qbehaviour/&lt;br /&gt;
* $mmQuestionDelegateProvider: You can use this provider to register your own question handlers to be used in a quiz or other places.  Addons should be placed in www/addons/qtype/&lt;br /&gt;
* $mmaModAssignFeedbackDelegate: You can use this provider to register your assign feedback plugins to be used in an assignment.  Addons should be placed in www/addons/mod/assign/feedback&lt;br /&gt;
* $mmaModAssignSubmissionDelegate: You can use this provider to register your assign submission plugins to be used in an assignment.  Addons should be placed in www/addons/mod/assign/submission&lt;br /&gt;
* $mmUserProfileFieldsDelegate: You can use this provider to register your user profile fields to be used in user profile.  Addons should be placed in www/addons/userprofilefield&lt;br /&gt;
* $mmaMessageOutputDelegate: You can use this service to register message output processors to be used in places like notification preferences. Addons should be placed in www/addons/messageoutput/&lt;br /&gt;
&lt;br /&gt;
Finally, the app has some other delegates you can use:&lt;br /&gt;
&lt;br /&gt;
* $mmInitDelegate: You can register processes to be executed while the app is being initialized. It cannot be used from remote addons since they&#039;re executed after the init process has ended.&lt;br /&gt;
* $mmContentLinksDelegate: The addons registered in here will receive content URLs (from notifications or links clicked) and they need to define which actions can be done for that URL. In the case of links clicked, the first action returned will be automatically triggered. You can use this delegate to intercept clicks on certain URLs and make them redirect to your addon.&lt;br /&gt;
* $mmPushNotificationsDelegate: You can use this service to be notified when a push notification is clicked.&lt;br /&gt;
* $mmCoursePrefetchDelegate: You should use this provider if your module addon should be downloadable.&lt;br /&gt;
* $mmFileUploaderDelegate: You can use this provider to add new ways to upload a file.&lt;br /&gt;
* $mmCronDelegate: You can use this service to register tasks that should be executed periodically.&lt;br /&gt;
* $mmURLDelegate: You can use this service to be notified when the app is launched using the Custom URL Scheme cordova plugin.&lt;br /&gt;
&lt;br /&gt;
=== Log ===&lt;br /&gt;
&lt;br /&gt;
We use angular’s $log service to log app messages.&lt;br /&gt;
&lt;br /&gt;
A decorator has been applied to $log to add the current date and time and the component to the $log service. If the following instruction is used:&lt;br /&gt;
&lt;br /&gt;
 $log.debug(“My message”)&lt;br /&gt;
&lt;br /&gt;
A message like the following is written in the log:&lt;br /&gt;
&lt;br /&gt;
 4/27/2015, 11:31:33 AM : My message&lt;br /&gt;
&lt;br /&gt;
To add the component to the message, the following must be done before using $log:&lt;br /&gt;
&lt;br /&gt;
 $log = $log.getInstance(“Component”)&lt;br /&gt;
&lt;br /&gt;
From now on the $log messages will have the component, so using the previous $log.debug instruction would print the following now:&lt;br /&gt;
&lt;br /&gt;
 4/27/2015, 11:31:33 AM Component: My message&lt;br /&gt;
&lt;br /&gt;
Log can be enabled and disabled. The default value for this is defined in the constant mmCoreDebugDefault.&lt;br /&gt;
&lt;br /&gt;
The log can be enabled/disabled in the app no matter the value of mmCoreDebugDefault. To do so, the following instruction needs  to be used:&lt;br /&gt;
&lt;br /&gt;
 $mmLog.enabled(true/false)&lt;br /&gt;
That change is permanent, so if the app is closed it’s going to remember if the log was enabled or disabled.&lt;br /&gt;
&lt;br /&gt;
There are 5 levels of messages:&lt;br /&gt;
&lt;br /&gt;
* debug: To write messages for debug purposes. It’s the most used in the app.&lt;br /&gt;
* error: To log error messages.&lt;br /&gt;
* warn: To log warnings (for example, it is used to warn that the Moodle version should be updated).&lt;br /&gt;
* log: To log a generic message. Not used.&lt;br /&gt;
* info: To write an info message.&lt;br /&gt;
&lt;br /&gt;
=== Events ===&lt;br /&gt;
&lt;br /&gt;
MoodleMobile has a $mmEvents service that allows sending and listening to events in the app. There are two type of events:&lt;br /&gt;
&lt;br /&gt;
* Unique: Thrown only once in the app&#039;s lifetime. If a listener is set after the event has been thrown, its callback is going to be called immediately.&lt;br /&gt;
* Regular: Can be thrown several times in the app&#039;s lifetime. Listeners callbacks will only be called when the event is triggered.&lt;br /&gt;
&lt;br /&gt;
These are the several regular events defined in the app:&lt;br /&gt;
&lt;br /&gt;
* mmCoreEventSessionExpired: Thrown when the user token expires. Param: a string with the site id that expired.&lt;br /&gt;
* mmCoreEventLogin: Thrown when the user logins in the app. It is also thrown when the app is started and the user session is restored.&lt;br /&gt;
* mmCoreEventLogout: Thrown when the user logouts (go to sites screen).&lt;br /&gt;
* mmCoreEventLanguageChanged: Thrown when the language is changed.&lt;br /&gt;
* mmCoreEventSiteAdded: Thrown when a new site is added.&lt;br /&gt;
* mmCoreEventSiteUpdated: Thrown when a site&#039;s data is updated. Params: a string with the site id that was updated.&lt;br /&gt;
* mmCoreEventSiteDeleted: Thrown when a site is deleted. Params: site deleted.&lt;br /&gt;
* mmCoreEventQueueEmpty: Thrown when the filepool queue is empty.&lt;br /&gt;
* Filepool file: Thrown when a filepool file is downloaded or its download fails. The event name is unique for each file. Params: an object with &#039;&#039;success&#039;&#039; property indicating if the download was a success or not.&lt;br /&gt;
* mmCoreEventCompletionModuleViewed: Thrown when a module with completion enabled is viewed. Params: courseid.&lt;br /&gt;
* mmCoreEventKeyboardShow: Thrown when the keyboard is shown. Params: same as &#039;native.keyboardshow&#039; event from [https://github.com/driftyco/ionic-plugin-keyboard ionic-plugin-keyboard].&lt;br /&gt;
* mmCoreEventKeyboardHide: Thrown when the keyboard is hidden. Params: same as &#039;native.keyboardhide&#039; event from [https://github.com/driftyco/ionic-plugin-keyboard ionic-plugin-keyboard].&lt;br /&gt;
* mmCoreEventUserDeleted: Thrown when we detect that a user has been deleted. Params: an object with siteid and the params sent to the WS call that returned the deleted user error.&lt;br /&gt;
* mmCoursesEventMyCoursesUpdated: Thrown when the my courses list should be updated. Params: site id.&lt;br /&gt;
* mmaModForumNewDiscussionEvent: Thrown when a discussion is created in the app. Params: forum id, new discussion id and forum cmid.&lt;br /&gt;
* mmCoreEventPackageStatusChanged: Thrown when the status of a package changes (e.g. a module). Params: siteid, component, componentId and new status.&lt;br /&gt;
* mmCoreEventSectionStatusChanged: Thrown when the status of a course section changes. Params: siteid and sectionid.&lt;br /&gt;
&lt;br /&gt;
=== Core directives ===&lt;br /&gt;
&lt;br /&gt;
MoodleMobile has a set of core directives that can be used by any addon or component. These are some of them.&lt;br /&gt;
&lt;br /&gt;
==== mmAutoFocus ====&lt;br /&gt;
&lt;br /&gt;
Sometimes it&#039;s useful to autofocus some input element when a view is loaded, showing the device&#039;s keyboard. You just have to add a &#039;&#039;&#039;mm-auto-focus&#039;&#039;&#039; attribute to your input element and this directive will handle it.&lt;br /&gt;
&lt;br /&gt;
==== mmBrowser ====&lt;br /&gt;
&lt;br /&gt;
Links that are not part of the app (e.g. not buttons) should be opened in a separate browser or app, depending on the platform they are being opened with. A directive was created to make this easy:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;mm-browser&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This will catch a click on the link and open it as it should, whether it opens a file, a URL, etc…&lt;br /&gt;
&lt;br /&gt;
Please note that the directive mm-format-text automatically applies mm-browser to all the links found.&lt;br /&gt;
&lt;br /&gt;
Since Moodle Mobile 2.9 the directive mmBrowser uses $mmContentLinksDelegate to detect if the URL belongs to any addon.&lt;br /&gt;
&lt;br /&gt;
==== mmCompletion ====&lt;br /&gt;
&lt;br /&gt;
This directive is meant for activity completion but it can probably be adapted to other uses.&lt;br /&gt;
&lt;br /&gt;
It will display a checkbox showing the current status of the activity. It allows to manually change the status by clicking on it if the activity is configured to do so.&lt;br /&gt;
&lt;br /&gt;
==== mmExternalContent ====&lt;br /&gt;
&lt;br /&gt;
External content is anything that is not locally accessible by the app. If some content has to be made available offline, or saved locally for performance reason, you should be using $mmFilepool. This factory allows you to download files locally, add them to a queue to be downloaded asynchronously, invalidate the local copy, etc…&lt;br /&gt;
&lt;br /&gt;
You can also use the directive mm-external-content which will automatically do the job of adding the files to the queue if they are not find locally.&lt;br /&gt;
&lt;br /&gt;
Please note that at the moment it was decided that mm-external-content would ony work for content that is coming directly from Moodle (pluginfile.php), though it is possible for a developer to be using $mmFilepool with any kind of URLs.&lt;br /&gt;
&lt;br /&gt;
==== mmFile ====&lt;br /&gt;
&lt;br /&gt;
There are several places throughout the app where the user can see a list of files that can be opened or prefetched. This is what this directive is for.&lt;br /&gt;
&lt;br /&gt;
It requires the name and URL of the file, and it will display a list item with the file&#039;s name and icon, along with a button to prefetch or refresh the file. This row can be clicked to open the file.&lt;br /&gt;
&lt;br /&gt;
Please notice that these files won&#039;t be downloaded unless the user opens or prefetches them.&lt;br /&gt;
&lt;br /&gt;
==== mmFormatText ====&lt;br /&gt;
&lt;br /&gt;
Some of the texts returned by Moodle need to be treated before being rendered since they can include filters, URLs, HTML, etc. You should apply this directive to any text liable to have these.&lt;br /&gt;
&lt;br /&gt;
You just have to wrap the text to format with this directive, something like this:&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;nowiki&amp;gt;&amp;lt;mm-format-text&amp;gt;{{ content }}&amp;lt;/mm-format-text&amp;gt;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
mmFormatText accepts several parameters to define its behaviour. These are the most important:&lt;br /&gt;
&lt;br /&gt;
* watch: Set it to true if the variable&#039;s value could change after the view is rendered. If you set it to false or you don&#039;t set it and the variable&#039;s value change then the changes won&#039;t be reflected in the view.&lt;br /&gt;
* after-render: Function to be called when the contents are rendered.&lt;br /&gt;
* clean: True if all HTML tags should be removed.&lt;br /&gt;
* singleline: True if new lines should be removed (all the text in a single line). Only valid if clean=true.&lt;br /&gt;
* shorten: To shorten the text. If a number is supplied then it will shorten the text to that number of characters. If a percentage is supplied then the number of characters to short will be the percentage of element&#039;s width. E.g. 50% of an element with 1000px width = 500 characters. Default value is 30%.&lt;br /&gt;
* expand-on-click: True if content should be expanded on click (undo shorten). Only valid if shorten=true.&lt;br /&gt;
* fullview-on-click: True if full content should be opened in a new view on click. Only valid if shorten=true.&lt;br /&gt;
&lt;br /&gt;
==== mmIframe ====&lt;br /&gt;
&lt;br /&gt;
Iframes can be annoying in Mobile, especially in iOS. This directive is meant to make your life easier when dealing with them.&lt;br /&gt;
&lt;br /&gt;
The non-relative links inside the iframe will be opened using an external browser. Relative links will be opened inside the iframe.&lt;br /&gt;
&lt;br /&gt;
Please notice that all popups opened by the iframe will be blocked since MoodleMobile doesn&#039;t support popups.&lt;br /&gt;
&lt;br /&gt;
==== mmImageViewer ====&lt;br /&gt;
&lt;br /&gt;
It&#039;s usual to shrink some images in Mobile to make them fit the device&#039;s width and height. With this directive you can use an icon or link to see a certain image at full size with scrolling.&lt;br /&gt;
&lt;br /&gt;
Please notice that mmFormatText automatically resizes images to fit the device screen and adds this directive to a magnifying glass icon.&lt;br /&gt;
&lt;br /&gt;
==== mmLoading ====&lt;br /&gt;
&lt;br /&gt;
In most cases the data to show in a view is retrieved from a Moodle site. These operations can take some time, so you should show a loading to show the user that the app is doing something.&lt;br /&gt;
&lt;br /&gt;
This directive will replace your content with a loading spinner until your data is ready. You just need to wrap your HTML code inside this directive:&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;nowiki&amp;gt;&amp;lt;mm-loading hide-until=&amp;quot;eventLoaded&amp;quot;&amp;gt; &amp;lt;ul class=&amp;quot;list&amp;quot;&amp;gt; ... &amp;lt;/ul&amp;gt; &amp;lt;/mm-loading&amp;gt;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You need to use the &#039;&#039;hide-until&#039;&#039; attribute to tell the directive when is your data ready. In the example above, when the scope&#039;s eventLoaded variable is set to true then the spinner will disappear and the content will be shown.&lt;br /&gt;
&lt;br /&gt;
You can configure the message to show while the spinner is shown (by default, &amp;quot;Loading&amp;quot;) and also the spinner&#039;s padding top.&lt;br /&gt;
&lt;br /&gt;
==== mmNavigationBar ====&lt;br /&gt;
&lt;br /&gt;
This directive shows a navigation bar with 3 possible buttons: an arrow right, an arrow left and a &amp;quot;info&amp;quot; button.&lt;br /&gt;
&lt;br /&gt;
You can see it in action in IMSCP and SCORM modules.&lt;br /&gt;
&lt;br /&gt;
==== mmNoInputValidation ====&lt;br /&gt;
&lt;br /&gt;
Sometimes we might want to disable automatic validation on some input fields (like URLs). This directive allows us to do so.&lt;br /&gt;
&lt;br /&gt;
==== mmSplitView and mmSplitViewLink ====&lt;br /&gt;
&lt;br /&gt;
It&#039;s usual in Mobile apps that in a tablet device you can see 2 views at once in two different panes, while in a smartphone you need to navigate through them 1 by 1. The split view directive allows us to achieve this.&lt;br /&gt;
&lt;br /&gt;
The split view has two panes: the left one (parent or menu) and the right one (child). In tablet the user will be able to see both panels at the same time, but in a smartphone they will be seen as two different views, one at once. Each pane will have its own state, view and controller.&lt;br /&gt;
&lt;br /&gt;
You should place the mm-split-view directive in the template of the &#039;&#039;&#039;left&#039;&#039;&#039; pane&#039;s state. You need to wrap all the HTML that should be in the left pane inside this directive:&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;nowiki&amp;gt;&amp;lt;mm-split-view component=&amp;quot;mmaModForumDiscussionsList&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;!-- CONTENT TO SHOW ON THE LEFT PANE (MENU) --&amp;gt;&lt;br /&gt;
    &amp;lt;/mm-split-view&amp;gt;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It&#039;s important to supply a unique component to the directive to prevent collisions with other split views.&lt;br /&gt;
&lt;br /&gt;
To change the right pane contents, the mmSplitViewLink directive is needed. If you want that the right pane contents change when the user clicks on some links in the left pane, then these links need to have the mm-split-view-link attribute with the state to load. Example:&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;nowiki&amp;gt;&amp;lt;mm-split-view component=&amp;quot;mmaModForumDiscussionsList&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;a mm-split-view-link=&amp;quot;site.mod_forum-discussion({cid: courseid, discussionid: discussion.discussion})&amp;quot;&amp;gt;&amp;lt;/a&amp;gt;&lt;br /&gt;
    &amp;lt;/mm-split-view&amp;gt;&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the example above, when the link is clicked the state &#039;site.mod_forum-discussion&#039; will be loaded in the right pane.&lt;br /&gt;
&lt;br /&gt;
The mmSplitView directive will try to load the first mmSplitViewLink when the view is loaded. You can change the link to load and when it should be loaded by using the attributes &amp;quot;load&amp;quot; and &amp;quot;loadWhen&amp;quot;. If you don&#039;t have access to the directive&#039;s scope but you still want to configure it then you can use the mmCoreSplitViewLoad event. When the directive receives this event it will try to immediately load the link supplied (if no link is supplied then it will load the first link found). Example to load the second link:&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;nowiki&amp;gt;$rootScope.$broadcast(mmCoreSplitViewLoad, {load: 2});&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Important considerations:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* The directive mm-split-view-link needs to be placed inside a mm-split-view, otherwise it won&#039;t work. Both directives need to be in the left pane template, placing any of them in the right pane template can cause errors and unexpected behaviour.&lt;br /&gt;
* Due to a limitation in Angular ui-router, the left pane state and the right pane state should NOT have parameters with the same name but different value, it can cause unexpected behaviors. Example: if the left pane loads a state with param &#039;courseid&#039;, then all the states that can be loaded in the right pane should avoid having a parameter named &#039;courseid&#039;. The right pane state can have a &#039;courseid&#039; param only if it will always have the same value than in left pane state. This is due to state hierarchy in ui-router.&lt;br /&gt;
&lt;br /&gt;
=== URL scheme API ===&lt;br /&gt;
&lt;br /&gt;
The Moodle Mobile app can be opened using links via the app&#039;s custom URL scheme:&lt;br /&gt;
&lt;br /&gt;
Links are of the form &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;moodlemobile://link=https://yourmoodlesite.org/mod/...&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For example, the link &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;moodlemobile://link=https://mysite.es/mod/choice/view.php?id=8&amp;lt;/syntaxhighlight&amp;gt; will open the mobile app and display a choice activity.&lt;br /&gt;
&lt;br /&gt;
You can specify an user (if you can guess the current user username) &amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;moodlemobile://link=jsmith@https://mysite.es/mod/choice/view.php?id=8&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the user has the mobile app installed in his device and he is logged in the site or he added the site, the app will display the resource required. If the site wasn&#039;t added, the app will require the user credentials for that site.&lt;br /&gt;
&lt;br /&gt;
== Development ==&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Plugins Development]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
* https://github.com/moodlehq/moodlemobile2&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Creating your custom app ==&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Customization]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Moodle Mobile Customization]]&lt;br /&gt;
* [[Moodle Mobile Plugins Development]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Developing_a_plugin_tutorial&amp;diff=62142</id>
		<title>Moodle Mobile 1 Developing a plugin tutorial</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Developing_a_plugin_tutorial&amp;diff=62142"/>
		<updated>2022-04-29T07:08:55Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 1}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
This tutorial describes all the steps needed for developing a plugin for the MoodleMobile app.&lt;br /&gt;
&lt;br /&gt;
In this tutorial we make the following assumptions:&lt;br /&gt;
* You have a medium/good skills/understand of Javascript, PHP and Moodle architecture&lt;br /&gt;
* You know what a Web Service is and how it works in Moodle&lt;br /&gt;
* The plugin you are going to develop requires a Web Service in your Moodle installation to work&lt;br /&gt;
&#039;&#039;&#039;Notice that neither the moodle plugin code and the moodle mobile plugin code are suitable for production environments&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
There is a second part of this tutorial where we extend the current WebServices and create a new &amp;quot;forums&amp;quot; plugin, see [[Moodle Mobile Developing a plugin tutorial part 2]]&lt;br /&gt;
== Plugin spec ==&lt;br /&gt;
We want to develop a &amp;quot;grades&amp;quot; plugin that will display the current user grades for all the gradable activities in a course the user is enroll in.&lt;br /&gt;
&lt;br /&gt;
A screenshot of the desired result:&lt;br /&gt;
&lt;br /&gt;
[[File:MoodleMobileTutorial01.png]]&lt;br /&gt;
&lt;br /&gt;
As you can see there is a new menu called &amp;quot;Grades&amp;quot; that displays a page in the middle panel showing all the course activities. When we click in an activity, the right panel shows the grade / maximum grade for the current user.&lt;br /&gt;
&lt;br /&gt;
In order to achieve this we will have to develop:&lt;br /&gt;
* A plugin for Moodle containing a new Web Service that returns grades and..&lt;br /&gt;
* A MoodleMobile plugin&lt;br /&gt;
== Steps ==&lt;br /&gt;
=== Set up your development environment ===&lt;br /&gt;
Clone or fork the official git repository [https://github.com/moodlehq/moodlemobile] in a local directory.&lt;br /&gt;
&lt;br /&gt;
Set up your development browser, see [[Moodle_Mobile]], I mainly use the Google Chrome browser.&lt;br /&gt;
=== Set up your Moodle installation ===&lt;br /&gt;
Enable debugging, disable caches, etc... the typical settings for developing.&lt;br /&gt;
&lt;br /&gt;
Enable the Web Services and also the protocols you are going to use for testing and developing (REST is mandatory). See [[Web Services]] documentation.&lt;br /&gt;
&lt;br /&gt;
Enable the Mobile Service&lt;br /&gt;
&lt;br /&gt;
Enable the moodle/webservice:createtoken capability for the Authenticated User Role at System Context&lt;br /&gt;
=== Develop the Moodle local/mod/block/... plugin ===&lt;br /&gt;
You can find the full source code here:&lt;br /&gt;
https://github.com/cvaconsulting/moodle-local_custommm&lt;br /&gt;
&lt;br /&gt;
In this case we&#039;ve developed a local plugin that contains a Web Service that returns grades as described here: https://tracker.moodle.org/browse/MDL-30085&lt;br /&gt;
&lt;br /&gt;
Notice that my version has some differences with the previous mentioned one. I have fixed some small bugs and changed some parameters.&lt;br /&gt;
=== Test your Web Services using a demo client ===&lt;br /&gt;
Before continuing you must test that your Web Service works, the local plugin contains a demo REST client, see:&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodle-local_custommm/blob/master/client/client.php&lt;br /&gt;
=== Create your custom Service ===&lt;br /&gt;
A service is a set of functions for an specific purpose. Since Moodle 2.1 there is a pre-built service that contains all the functions needed for the official mobile app.&lt;br /&gt;
&lt;br /&gt;
This service cannot be edited for adding new functions so we don&#039;t have a simple way for make available our new Web Service function to the mobile app.&lt;br /&gt;
&lt;br /&gt;
The only way for fix this is creating a new Service in Moodle, and configuring our app for pointing to this new service.&lt;br /&gt;
&lt;br /&gt;
There are two ways of creating a Service in Moodle:&lt;br /&gt;
* Using a db/services.php in your plugin as mentioned here: [[External services description]]&lt;br /&gt;
&lt;br /&gt;
* Creating the service in your Moodle installation using the forms in Admin &amp;gt; Plugins &amp;gt; Web Services &amp;gt; External Services&lt;br /&gt;
In this case we&#039;ve created the service using the Moodle forms because it gives more flexibility in some aspects, there is a big impediment, due to https://tracker.moodle.org/browse/MDL-29807 in order to add a shortname for the new Service, we have to edit manually the Moodle database for adding the shortname in the dbprefix_external_services table&lt;br /&gt;
&lt;br /&gt;
[[File:MoodleMobileTutorial02.png]]&lt;br /&gt;
&lt;br /&gt;
When creating, the flats &amp;quot;Can download files&amp;quot; and &amp;quot;Enabled&amp;quot; must be checked.&lt;br /&gt;
=== Set up your MoodleMobile client ===&lt;br /&gt;
The most important change you have to do is edit the config.json file and change the wsservice parameter in order to point to the shortname of the Service you created before.&lt;br /&gt;
&lt;br /&gt;
You have to add also the name of the plugin you are developing at the final of the plugins parameter:&lt;br /&gt;
&lt;br /&gt;
&amp;quot;plugins&amp;quot; : [&amp;quot;notifications&amp;quot;, &amp;quot;upload&amp;quot;, &amp;quot;contents&amp;quot;, &amp;quot;participants&amp;quot;, &amp;quot;addcontact&amp;quot;, &amp;quot;addnote&amp;quot;, &amp;quot;sendmessage&amp;quot;, &amp;quot;grades&amp;quot;],&lt;br /&gt;
&lt;br /&gt;
Notice that, for avoid errors, this last change should be done once the file plugins/grades/main.js file exists&lt;br /&gt;
=== Develop the MoodleMobile plugin ===&lt;br /&gt;
The full source code can be found here:&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-grades&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-grades/blob/master/lang/en.json - Contains the language strings, the string &amp;quot;pluginPLUGINname&amp;quot; is mandatory&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-grades/blob/master/icon.png - The plugin icon&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-grades/blob/master/activities.html - The template for the activities list (center panel)&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-grades/blob/master/activitygrade.html - The template for showing the grade for an activity (right panel)&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-grades/blob/master/main.js - The plugin&#039;s main code&lt;br /&gt;
&lt;br /&gt;
Bellow, I summarize the most important parts of the code (always parts of the main.js file):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var templates = [&lt;br /&gt;
    &amp;quot;root/externallib/text!root/plugins/grades/activities.html&amp;quot;,&lt;br /&gt;
    &amp;quot;root/externallib/text!root/plugins/grades/activitygrade.html&amp;quot;,&lt;br /&gt;
    &amp;quot;root/externallib/text!root/plugins/grades/lang/en.json&amp;quot;&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
define(templates,function (activities, activityGrade, langStrings) {&lt;br /&gt;
    var plugin = {&lt;br /&gt;
        settings: {&lt;br /&gt;
            name: &amp;quot;grades&amp;quot;,&lt;br /&gt;
            type: &amp;quot;course&amp;quot;,&lt;br /&gt;
            menuURL: &amp;quot;#course/grades/&amp;quot;,&lt;br /&gt;
            lang: {&lt;br /&gt;
                component: &amp;quot;local_custommm&amp;quot;,&lt;br /&gt;
				strings: langStrings&lt;br /&gt;
            }&lt;br /&gt;
        },&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice that we required three files, the two templates (.html) and the .json file that contains the lang strings.&lt;br /&gt;
&lt;br /&gt;
In the plugin.settings structure, we define the lant attribute that contains two very important attributes:&lt;br /&gt;
* Component: It must refer to the Moodle component linked to this plugin&lt;br /&gt;
* strings: The lang strings in .json format (plain text). As you can see, with this line we are populating the plugin.settings.lang.strings var withe the contents of the lang/en.json files&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        viewActivities: function(courseId) {&lt;br /&gt;
&lt;br /&gt;
            MM.panels.showLoading(&#039;center&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (MM.deviceType == &amp;quot;tablet&amp;quot;) {&lt;br /&gt;
                MM.panels.html(&#039;right&#039;, &#039;&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            var data = {&lt;br /&gt;
            &amp;quot;options[0][name]&amp;quot; : &amp;quot;&amp;quot;,&lt;br /&gt;
            &amp;quot;options[0][value]&amp;quot; : &amp;quot;&amp;quot;&lt;br /&gt;
            };            &lt;br /&gt;
            data.courseid = courseId;&lt;br /&gt;
            &lt;br /&gt;
            MM.moodleWSCall(&#039;core_course_get_contents&#039;, data, function(contents) {&lt;br /&gt;
                var course = MM.db.get(&amp;quot;courses&amp;quot;, MM.config.current_site.id + &amp;quot;-&amp;quot; + courseId);&lt;br /&gt;
&lt;br /&gt;
                var tpl = {&lt;br /&gt;
                    sections: contents,&lt;br /&gt;
                    course: course.toJSON() // Convert a model to a plain javascript object.&lt;br /&gt;
                }&lt;br /&gt;
                var html = MM.tpl.render(MM.plugins.grades.templates.activities.html, tpl);&lt;br /&gt;
                MM.panels.show(&amp;quot;center&amp;quot;, html);&lt;br /&gt;
            });&lt;br /&gt;
        },&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The viewActivities is the function that displays in the center panel the list of activities inside a course, as you can see the code is pretty simple. &lt;br /&gt;
* We call to the core_course_get_contents Web Service that returns all the activities and resources inside a course&lt;br /&gt;
* We render the template that will iterate over the sections and activities displaying the links to specifics activities&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
         viewActivityGrade: function(activityName, courseId, modName, cmId) {&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (MM.deviceType == &amp;quot;tablet&amp;quot;) {&lt;br /&gt;
                MM.panels.showLoading(&#039;right&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            var data = {&lt;br /&gt;
                &amp;quot;grades[courseid]&amp;quot; : courseId,&lt;br /&gt;
                &amp;quot;grades[component]&amp;quot; : &amp;quot;mod_&amp;quot; + modName,&lt;br /&gt;
                &amp;quot;grades[cmid]&amp;quot; : cmId,&lt;br /&gt;
                &amp;quot;grades[userids][0]&amp;quot; : MM.config.current_site.userid&lt;br /&gt;
            };            &lt;br /&gt;
            &lt;br /&gt;
            MM.moodleWSCall(&#039;local_custommm_get_grades&#039;, data, function(contents) {&lt;br /&gt;
                var course = MM.db.get(&amp;quot;courses&amp;quot;, MM.config.current_site.id + &amp;quot;-&amp;quot; + courseId);&lt;br /&gt;
                &lt;br /&gt;
                var grade= MM.lang.s(&amp;quot;nogrades&amp;quot;, &amp;quot;grades&amp;quot;);&lt;br /&gt;
                &lt;br /&gt;
                if(contents.items[0]) {&lt;br /&gt;
                    grade = contents.items[0][&amp;quot;grades&amp;quot;][0][&amp;quot;str_long_grade&amp;quot;];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                var tpl = {&lt;br /&gt;
                    activityName: activityName,&lt;br /&gt;
                    sections: contents,&lt;br /&gt;
                    finalGrade: grade,&lt;br /&gt;
                    course: course.toJSON(), // Convert a model to a plain javascript object.&lt;br /&gt;
                    items: contents&lt;br /&gt;
                }&lt;br /&gt;
                var html = MM.tpl.render(MM.plugins.grades.templates.activityGrade.html, tpl);&lt;br /&gt;
                MM.panels.show(&amp;quot;right&amp;quot;, html);&lt;br /&gt;
                if (MM.deviceType == &amp;quot;tablet&amp;quot; &amp;amp;&amp;amp; contents.length &amp;gt; 0) {&lt;br /&gt;
                    // First section.&lt;br /&gt;
                    MM.plugins.contents.viewCourseContentsSection(courseId, 0);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        },&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This function gets and displays the grade for the selected activity in the centered panel.&lt;br /&gt;
&lt;br /&gt;
We just call to the Web Service we developed and parse the result, if nothing is returned we display a generic message indicating that there is no grades information for the activity.&lt;br /&gt;
If a grades full structure is returned, we return the first grade for the first grade items (as Moodle usually does) that corresponds to the user grade for the activity&lt;br /&gt;
&lt;br /&gt;
Notice that:&lt;br /&gt;
* Some activities, like workshop, returns two different grades items (teacher and peer grades). The example doesn&#039;t implement any logic for this case.&lt;br /&gt;
* Some activities may return outcomes. The example doesn&#039;t implement any logic for this case.&lt;br /&gt;
* The rest grade information (grade min, grade max, feedback) is also covered by this example.&lt;br /&gt;
=== Deliver your plugin ===&lt;br /&gt;
Android: Package your plugin using &#039;&#039;PhoneGap build&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
iOs: Further instructions soon&lt;br /&gt;
== See also ==&lt;br /&gt;
[[Moodle Mobile Developing a plugin tutorial part 2]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1&amp;diff=62139</id>
		<title>Moodle Mobile 1</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1&amp;diff=62139"/>
		<updated>2022-04-29T07:06:02Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;For user documentation see &#039;&#039;&#039;[[:en:Moodle Mobile|Moodle Mobile]]&#039;&#039;&#039;.&amp;lt;/p&amp;gt;&lt;br /&gt;
{{Moodle Mobile 1}}&lt;br /&gt;
&lt;br /&gt;
IMPORTANT: This documentation is for the former official version of the Moodle Mobile application (not maintained anymore and not available in the stores), documentation for the new version is available in [[Moodle Mobile]]&lt;br /&gt;
&lt;br /&gt;
== General Overview ==&lt;br /&gt;
&lt;br /&gt;
MM (Moodle Mobile) is the official mobile app for Moodle. MM is an HTML5 app that uses common web technologies.&lt;br /&gt;
&lt;br /&gt;
Basically, is a Web Services client that uses REST as protocol to obtain and send information to your Moodle installation/s.&lt;br /&gt;
&lt;br /&gt;
The layout is created using HTML5 and CSS3, interaction with the phone and packaging is done using Phonegap.&lt;br /&gt;
&lt;br /&gt;
For calling the Web Services, manipulating the DOM and interacting with Phonegap, we use jQuery as our Javascript framework.&lt;br /&gt;
&lt;br /&gt;
This app is a replacement of the old [[Mobile app]], see the discussion [https://moodle.org/mod/forum/discuss.php?d=206736 Change in our Moodle mobile app strategy] for details.&lt;br /&gt;
&lt;br /&gt;
== Features ==&lt;br /&gt;
&lt;br /&gt;
The features are going to be the same that the old [[Mobile app]] plus:&lt;br /&gt;
&lt;br /&gt;
* PUSH Notifications (only iOs in first version)&lt;br /&gt;
* Mobile and tablet version (Responsive design)&lt;br /&gt;
* Support for contrib plugins&lt;br /&gt;
* Support for external settings: Extra CSS&lt;br /&gt;
* New development and debugging features&lt;br /&gt;
&lt;br /&gt;
Features for future versions:&lt;br /&gt;
&lt;br /&gt;
* Calendar sync&lt;br /&gt;
* Offline browsing and posting in forums&lt;br /&gt;
* Offline grading&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
&lt;br /&gt;
* Moodle 2.4&lt;br /&gt;
&lt;br /&gt;
Future versions will require the last minor versions if Web Services are not backported.&lt;br /&gt;
&lt;br /&gt;
== Technologies used ==&lt;br /&gt;
&lt;br /&gt;
* HTML5 http://www.w3.org/TR/2011/WD-html5-20110525/&lt;br /&gt;
&lt;br /&gt;
* CSS3 http://www.w3.org/Style/CSS/&lt;br /&gt;
&lt;br /&gt;
* Media queries for screen width and height http://www.w3.org/TR/css3-mediaqueries/&lt;br /&gt;
&lt;br /&gt;
* Phonegap http://wiki.phonegap.com/w/page/16494772/FrontPage&lt;br /&gt;
&lt;br /&gt;
* jQuery http://jquery.com/&lt;br /&gt;
&lt;br /&gt;
* jQuery UI http://jqueryui.com/&lt;br /&gt;
&lt;br /&gt;
* jQuery touchSwipe http://labs.skinkers.com/touchSwipe/&lt;br /&gt;
&lt;br /&gt;
* matchMedia https://github.com/paulirish/matchMedia.js/&lt;br /&gt;
&lt;br /&gt;
* Backbone and Underscore http://backbonejs.org/&lt;br /&gt;
&lt;br /&gt;
* RequireJS requirejs.org&lt;br /&gt;
&lt;br /&gt;
* jsdoc http://code.google.com/p/jsdoc-toolkit/&lt;br /&gt;
&lt;br /&gt;
* Google Javascript Style Guide http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml&lt;br /&gt;
&lt;br /&gt;
* Google Closure Lint http://code.google.com/p/closure-linter/&lt;br /&gt;
&lt;br /&gt;
=== Phonegap ===&lt;br /&gt;
&lt;br /&gt;
MM uses Phonegap for using Moodle hardware/software features like Camera, Audio/video recorder, access to the file system, etc.&lt;br /&gt;
Phonegap is loaded in the index page of the app (jQuery, the main lib of the app are also loaded in the index page).&lt;br /&gt;
&lt;br /&gt;
=== jQuery ===&lt;br /&gt;
&lt;br /&gt;
For DOM manipulation and some helper functions, is a requirement of others libraries listed below.&lt;br /&gt;
&lt;br /&gt;
=== jQuery UI ===&lt;br /&gt;
&lt;br /&gt;
Dialogs and also for enhancing forms elements (buttons, checkboxes, etc...)&lt;br /&gt;
&lt;br /&gt;
=== jQuery touchSwipe === &lt;br /&gt;
&lt;br /&gt;
For detecting mouse gestures&lt;br /&gt;
&lt;br /&gt;
=== matchMedia ===&lt;br /&gt;
&lt;br /&gt;
Javascript detection of media queries&lt;br /&gt;
&lt;br /&gt;
=== Backbone and Underscore ===&lt;br /&gt;
&lt;br /&gt;
For storage (Models and Collections), Underscore templates, and URL Routing&lt;br /&gt;
&lt;br /&gt;
=== RequireJS ===&lt;br /&gt;
&lt;br /&gt;
For handling modules dependencies and loading plugins&lt;br /&gt;
&lt;br /&gt;
== Structure of the app (only main directories) ==&lt;br /&gt;
&lt;br /&gt;
 css/ - App main layout and styles&lt;br /&gt;
 img/ - App images&lt;br /&gt;
 lang/ - Default lang&lt;br /&gt;
 lib/ - Main libraries&lt;br /&gt;
 plugins/ - Plugins&lt;br /&gt;
 test/ - Test data when developing in Test mode&lt;br /&gt;
 config.json - Main app configuration file (presets)&lt;br /&gt;
 index.html - Index page of the app&lt;br /&gt;
&lt;br /&gt;
== Getting and sending information to Moodle ==&lt;br /&gt;
&lt;br /&gt;
MM uses standard Moodle Web Services for getting and sending information to Moodle.&lt;br /&gt;
AJAX (jQuery) and REST + JSON are the technologies used.&lt;br /&gt;
&lt;br /&gt;
Notice that mobile HTML5 apps doesn’t have cross domain restrictions so you can make AJAX calls to any domain.&lt;br /&gt;
&lt;br /&gt;
== External settings ==&lt;br /&gt;
&lt;br /&gt;
In your Moodle installation you can specify an extra CSS file for customizing the app remotely.&lt;br /&gt;
&lt;br /&gt;
This CSS is downloaded and stored in the device database for further use.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Storage ==&lt;br /&gt;
&lt;br /&gt;
We use HTML5 localStorage that is cross browser implemented and there are libraries that can work of top of it.&lt;br /&gt;
&lt;br /&gt;
We use Backbone Models and Collections as a wrapper for localStorage.&lt;br /&gt;
&lt;br /&gt;
Note that localStorage has some limitations like a 5MB max size in some devices and also is considers &amp;quot;Temporal data&amp;quot; for some devices like the new iPhones.&lt;br /&gt;
&lt;br /&gt;
== Internationalization == &lt;br /&gt;
&lt;br /&gt;
The app includes a language file in JSON (English as the default language).&lt;br /&gt;
&lt;br /&gt;
Other language packs are available, full list here:&lt;br /&gt;
&lt;br /&gt;
https://github.com/moodlehq/moodlemobile/tree/master/lang&lt;br /&gt;
&lt;br /&gt;
For translating the app to your language, you can use the [http://lang.moodle.org AMOS tool] Translations from this tool are periodically synchronized with new versions in the app&lt;br /&gt;
&lt;br /&gt;
== Plugins ==&lt;br /&gt;
&lt;br /&gt;
=== Overview ===&lt;br /&gt;
&lt;br /&gt;
Plugins allow developers to extend the app functionalities.&lt;br /&gt;
A plugin it’s a subdirectory that implements a set of required functionalities.&lt;br /&gt;
&lt;br /&gt;
=== Types of plugins ===&lt;br /&gt;
&lt;br /&gt;
* General: Interactions over the global app, such as the Notifications, Upload, Help and Web&lt;br /&gt;
* Course: Interactions over a course, such as course contents or participants&lt;br /&gt;
* User: Interactions over an user, such as send a message, add as a contact, write a private note&lt;br /&gt;
* Settings: Additional settings for the app&lt;br /&gt;
&lt;br /&gt;
=== Development ===&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Plugins Development]].&lt;br /&gt;
&lt;br /&gt;
There are a couple of tutorials with two sample plugins (grades and forums plugins)&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Developing a plugin tutorial]] and [[Moodle Mobile Developing a plugin tutorial part 2]]&lt;br /&gt;
&lt;br /&gt;
Source code:&lt;br /&gt;
&lt;br /&gt;
* https://github.com/moodlehq/moodlemobile&lt;br /&gt;
&lt;br /&gt;
== Creating your custom app ==&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Customization]].&lt;br /&gt;
&lt;br /&gt;
== Limitations and disadvantages of HTML5 apps ==&lt;br /&gt;
&lt;br /&gt;
* Less speed and smoothness&lt;br /&gt;
&lt;br /&gt;
* Limited to the Mobile functionalities provided from Phonegap&lt;br /&gt;
&lt;br /&gt;
== FAQ ==&lt;br /&gt;
 &lt;br /&gt;
=== Is the app a replacement of the MyMobile theme? ===&lt;br /&gt;
No, see http://moodle.org/mod/forum/discuss.php?d=206736#p901751&lt;br /&gt;
&lt;br /&gt;
=== What is the difference between a native app and a Mobile specific theme or responsive theme? ===&lt;br /&gt;
&lt;br /&gt;
See http://moodle.org/mod/forum/discuss.php?d=206736#p901475&lt;br /&gt;
&lt;br /&gt;
Also http://moodle.org/mod/forum/discuss.php?d=206736#p901751&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Moodle Mobile Customization]]&lt;br /&gt;
* [[Moodle Mobile Plugins Development]]&lt;br /&gt;
* [[Moodle Mobile Building the app for Android]]&lt;br /&gt;
* [[Moodle Mobile Building the app for iOS]]&lt;br /&gt;
* [http://es.slideshare.net/juanleyva/creating-a-custom-moodle-mobile-app-moodle-moot-spain-2014 Creating a custom Moodle Mobile app - MoodleMoot Spain 2014]&lt;br /&gt;
&lt;br /&gt;
[[es:Moodle Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Customization&amp;diff=62138</id>
		<title>Moodle Mobile 1 Customization</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Customization&amp;diff=62138"/>
		<updated>2022-04-29T07:05:56Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle Mobile 1}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
In this document are described the general steps for building your custom Mobile app.&lt;br /&gt;
&lt;br /&gt;
You can find a step by step guide of how to create a custom version of the app here: http://es.slideshare.net/juanleyva/creating-a-custom-moodle-mobile-app-moodle-moot-spain-2014&lt;br /&gt;
== Creating your custom application ==&lt;br /&gt;
Remember that if you only want to change the global styles of the app, you can use an extra CSS file in your Moodle installation without needing to recompile the app.&lt;br /&gt;
=== Basic custom settings ===&lt;br /&gt;
In your Moodle installation you can specify a extra CSS file in the Mobile settings section&lt;br /&gt;
&lt;br /&gt;
You can edit the config.json file for:&lt;br /&gt;
- Adding a default language&lt;br /&gt;
- Add some presets in the login screen (url and username) pointing to your installation&lt;br /&gt;
=== Development environment ===&lt;br /&gt;
See [[#Testing_and_developing]]&lt;br /&gt;
&lt;br /&gt;
It is recommended that you have a local web server (app and moodle installation under localhost or the same local domain).&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
* A Moodle installation with Web Services (REST) enabled - see [[Web services]] documentation&lt;br /&gt;
:* Administration -&amp;gt; Plugins -&amp;gt; Web Services -&amp;gt; External Services -&amp;gt; Enable mobile web service&lt;br /&gt;
&lt;br /&gt;
:* Administration -&amp;gt; Plugins -&amp;gt; Web Services -&amp;gt; Protocols -&amp;gt; Enable REST&lt;br /&gt;
&lt;br /&gt;
:* Administration -&amp;gt; Users -&amp;gt; Permission -&amp;gt; Define roles -&amp;gt; Edit the Authenticated user role -&amp;gt; Allow webservice/rest:use &lt;br /&gt;
* A local copy of the Mobile HTML5 App with the Debugging options Enabled (https://github.com/moodlehq/moodlemobile)&lt;br /&gt;
=== Web Services Development ===&lt;br /&gt;
In most cases your plugin/s will need Web Services to get data from your Moodle installation. It is recommended that you first develop the Web Services that you will need (see [[Web Services]]).&lt;br /&gt;
* Create a local plugin in Moodle for adding the extra Web Services you need and also your plugin&#039;s lang files (Local WS plugin template: https://moodle.org/plugins/view.php?plugin=local_wstemplate)&lt;br /&gt;
&lt;br /&gt;
* Create a new External Service in your Moodle installation, adding a services.php file in your new local plugin or creating manually the service in your Moodle installation.(Plugins -&amp;gt; Web Services) See [[External services description]] for more info. Remember the key name of the service, because you will need it later.&lt;br /&gt;
=== Moodle Mobile Plugin Development ===&lt;br /&gt;
See [[Moodle Mobile 1 Plugins Development]] and [[Moodle Mobile 1 Developing a plugin tutorial]]&lt;br /&gt;
&lt;br /&gt;
For developing the plugin you need:&lt;br /&gt;
* Edit the /config.json file to:&lt;br /&gt;
:* Add the extra plugins you implement&lt;br /&gt;
:* Indicate the WS service name you are going to use in your Moodle installation (Remember, that you need to create a WS Service indicating the functions available for your custom app)&lt;br /&gt;
:* Add your custom default lang file (es, ca, fr ...) (the language file must exists in the app&#039;s lang/ folder)&lt;br /&gt;
:* Change the app login form presets&lt;br /&gt;
* Add your own css files, custom images, etc..&lt;br /&gt;
=== Building ===&lt;br /&gt;
There are three options for building the app, aka compile it to different devices:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Phonegap Build&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Phonegap Build&#039;&#039; is an online service that let you to build the app automatically for different platforms. The main advantage is that simplify a lot the process of building.&lt;br /&gt;
&lt;br /&gt;
You just put your customizations in a github account and Phonegaps automatically build the app for different platforms pulling your github code.&lt;br /&gt;
&lt;br /&gt;
Notice that the free version is for open repositories, but you can use the non-free version for private repositories.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Phonegap CLI&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You can build also using the Phonegap CLI interface, it requires to install the different Platform SDK / tools but the build process is simpler.&lt;br /&gt;
&lt;br /&gt;
Once installed the environment, you need to deploy in your project/www folder the app code&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Platform SDK&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Building the app for Android]] or [[Moodle Mobile Building the app for iOS]] (These articles may be obsolete)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Automatic build system&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Automatic Building System]] for some instructions for building an automatic building system initially for Android.&lt;br /&gt;
== Testing and developing ==&lt;br /&gt;
=== Chromium or Google Chrome browser ===&lt;br /&gt;
See [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
=== Ripple ===&lt;br /&gt;
Requirements: Google Chrome browser + Ripple mobile environment emulator plugin (http://ripple.tinyhippos.com/)&lt;br /&gt;
&lt;br /&gt;
You must run Google Chrome in Unsafe mode adding this param:&lt;br /&gt;
 --allow-file-access-from-files&lt;br /&gt;
IMPORTANT: I strongly recommend you create a new link or application launch called &amp;quot;Google Unsafe&amp;quot; and use it only for testing the app.&lt;br /&gt;
 &amp;quot;Path to chrome\chrome.exe&amp;quot; --allow-file-access-from-files&lt;br /&gt;
Open the index.html file in the Google Chrome unsafe and click on the Ripple icon to activate the emulator.&lt;br /&gt;
&lt;br /&gt;
Once opened in the Ripple settings block, change Cross Domain Proxy to Disabled.&lt;br /&gt;
&lt;br /&gt;
Please note that some functionalities (camera, audio recording, contact) will not work in the emulator.&lt;br /&gt;
=== Platform SDK ===&lt;br /&gt;
Install the Android or iPhone SDK.&lt;br /&gt;
=== Debugging styles or running a remote Console ===&lt;br /&gt;
You can use Safari or Google Chrome developer tools for debugging (console) and changing the CSS styles in live:&lt;br /&gt;
&lt;br /&gt;
https://developer.chrome.com/devtools/docs/remote-debugging&lt;br /&gt;
&lt;br /&gt;
http://phonegap-tips.com/articles/debugging-ios-phonegap-apps-with-safaris-web-inspector.html&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Local web server ===&lt;br /&gt;
If you deploy the html files in a server under the same domain that the Moodle you are going to connect to you can test the application without emulator or changing the Security settings of your browser.&lt;br /&gt;
&lt;br /&gt;
In other words&lt;br /&gt;
&lt;br /&gt;
Your Moodle at http://myhost.com/moodle24&lt;br /&gt;
&lt;br /&gt;
Your app at http://myhost.com/moodlemobile/&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Your Moodle at http://localhost/moodle24&lt;br /&gt;
&lt;br /&gt;
Your app at http://localhost/moodlemobile/&lt;br /&gt;
&lt;br /&gt;
Note that the mobile - related modules will not work.&lt;br /&gt;
== See also ==&lt;br /&gt;
[http://es.slideshare.net/juanleyva/creating-a-custom-moodle-mobile-app-moodle-moot-spain-2014 Creating a custom Moodle Mobile app - MoodleMoot Spain 2014]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Desktop_release_notes&amp;diff=62137</id>
		<title>Moodle Desktop release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Desktop_release_notes&amp;diff=62137"/>
		<updated>2022-04-29T07:05:05Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;Note: [https://moodle.org/mod/forum/discuss.php?d=413350 Moodle Desktop is discontinued] since 1 May 2021.&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Version name&lt;br /&gt;
! Date&lt;br /&gt;
! Platforms&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.7.2&lt;br /&gt;
| 20 September 2019&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.7.2_release_notes|Moodle Mobile 3.7.2]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.7.1&lt;br /&gt;
| -&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| Version skipped due to a security issue detected in Moodle App 3.7.1&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.7.0&lt;br /&gt;
| 12 Julio 2019&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.7.0_release_notes|Moodle Mobile 3.7.0]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.6.1&lt;br /&gt;
| 14 May 2019&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.6.1_release_notes|Moodle Mobile 3.6.1]] plus the following desktop-specific improvement:&lt;br /&gt;
* It is now possible to enable the &amp;quot;Developer Tools&amp;quot; in the Desktop app using the same key combinations that the Chrome browser: &amp;quot;Control + Shift + I&amp;quot; (in MacOs is Command + Option + I).&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.6.0&lt;br /&gt;
| 3 February 2019&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.6.0_release_notes|Moodle Mobile 3.6.0]] &lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.5.2&lt;br /&gt;
| 19 September 2018&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.5.2_release_notes|Moodle Mobile 3.5.2]] &lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.5.1&lt;br /&gt;
| 25 July 2018&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.5.1_release_notes|Moodle Mobile 3.5.1]] &lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.5.0&lt;br /&gt;
| -&lt;br /&gt;
| &lt;br /&gt;
-&lt;br /&gt;
| Skipped, see Moodle Desktop 3.5.1&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.4.2&lt;br /&gt;
| 20 March 2018&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.4.2_release_notes|Moodle Mobile 3.4.2]] &lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.4.1&lt;br /&gt;
| 1 March 2018&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.4.1_release_notes|Moodle Mobile 3.4.1]] &lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.4.0&lt;br /&gt;
| 1 December 2017&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.4.0_release_notes|Moodle Mobile 3.4.0]] &lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.3.2&lt;br /&gt;
| 29 September 2017&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.3.2_release_notes|Moodle Mobile 3.3.2]] plus:&lt;br /&gt;
* New 32 bits version for Linux&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.3.1&lt;br /&gt;
| 8 August 2017&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.3.1_release_notes|Moodle Mobile 3.3.1]] plus the following desktop-specific improvements:&lt;br /&gt;
* The app keeps the focus when re-opened via the status bar&lt;br /&gt;
* Scroll down on pages using a scroll wheel or trackpad gesture is now supported&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.3.0&lt;br /&gt;
| 5 July 2017&lt;br /&gt;
| Windows 10&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.3.0_release_notes|Moodle Mobile 3.3.0]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[:en:Moodle Desktop|Moodle Desktop user documentation]]&lt;br /&gt;
* [[Moodle Mobile release notes]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Desktop&amp;diff=62134</id>
		<title>Moodle Desktop</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Desktop&amp;diff=62134"/>
		<updated>2022-04-29T07:02:40Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
{{Moodle App (Ionic 3)}}&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;Note: [https://moodle.org/mod/forum/discuss.php?d=413350 Moodle Desktop is discontinued] since 1 May 2021.&amp;lt;/p&amp;gt;&lt;br /&gt;
[http://download.moodle.org/desktop/ Moodle Desktop] is build using the popular framework [http://electron.atom.io/ Electron].&lt;br /&gt;
&lt;br /&gt;
Electron is a very popular cross-platform for building desktop apps with JavaScript, HTML, and CSS.&lt;br /&gt;
&lt;br /&gt;
If you want to build your custom version of Moodle Desktop, please follow the Electron Guides that you will find in https://electron.atom.io/docs/&lt;br /&gt;
&lt;br /&gt;
Please, consider the following:&lt;br /&gt;
* For building a Windows version you will need a Windows computer.&lt;br /&gt;
* The Mac version will require a Mac computer.&lt;br /&gt;
* The Linux version may require a Linux distribution (depending on the selected target).&lt;br /&gt;
&lt;br /&gt;
Note also that if you want to publish your custom versions in the Windows or Apple stores, you will need to sign-up for a developer account (it costs money). &lt;br /&gt;
&lt;br /&gt;
Moodle HQ provides a branding service of Moodle Desktop via the [https://moodle.com/mobileapp/ The Branded Moodle Mobile App]. This service includes publishing in the stores and code signing.&lt;br /&gt;
&lt;br /&gt;
== I&#039;m feeling lucky ==&lt;br /&gt;
&lt;br /&gt;
If you are already familiar with Electron (or simply want to get your feet wet before diving deeper), you can get started easily. But first, you&#039;ll have to grab a copy of [https://github.com/moodlehq/moodlemobile2 Moodle Mobile 2] from Github. See our docs [https://docs.moodle.org/dev/Setting_up_your_development_environment_for_Moodle_Mobile_2#Clone_the_app_base_code here] if you need help.&lt;br /&gt;
&lt;br /&gt;
Once you have a copy, you&#039;ll see that there is a folder named &#039;&#039;desktop&#039;&#039; located in the root directory.&lt;br /&gt;
This is where the Electron configuration file for the desktop app is located (aptly named &amp;quot;electron.js&amp;quot;).&lt;br /&gt;
When we build our app, the distribution files will also be placed in this folder.&lt;br /&gt;
The &amp;quot;electron.js&amp;quot; file defines the configuration for the desktop app with sane defaults.&lt;br /&gt;
You can modify it if you want to (you don&#039;t really have to though) and use either of these scripts to build the desktop app :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;npm run desktop.pack&amp;lt;/syntaxhighlight&amp;gt; (only generates the package directory without really packaging it. This is useful for testing purposes)&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;npm run desktop.dist&amp;lt;/syntaxhighlight&amp;gt; (to package in a distributable format (e.g. dmg, windows installer, deb package))&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Deleting the profile ===&lt;br /&gt;
&lt;br /&gt;
Sometimes the app can get into a bad state and you might want to delete its data. When using the desktop environment, you need to delete the contents of the profile folder.&lt;br /&gt;
&lt;br /&gt;
* On Windows this can sometimes be found at C:\Users\ (your username) \AppData\Roaming\moodlemobile&lt;br /&gt;
&lt;br /&gt;
Just quit the app, delete all the files in this folder, and open it again; you&#039;ll start with a fresh app.&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Contributing_to_the_Moodle_app&amp;diff=62132</id>
		<title>Contributing to the Moodle app</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Contributing_to_the_Moodle_app&amp;diff=62132"/>
		<updated>2022-04-29T07:00:59Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:WillNotMigrate}}&lt;br /&gt;
&lt;br /&gt;
There are lots of ways you can contribute to the Moodle App project. If you want to see more ways to contribute to the Moodle project, please see [[Contributing_to_Moodle|Contributing to Moodle]].&lt;br /&gt;
&lt;br /&gt;
==User support==&lt;br /&gt;
&lt;br /&gt;
Join in with the forum discussions in [https://moodle.org/mod/forum/view.php?id=7798 Moodle for Mobile forum].&lt;br /&gt;
&lt;br /&gt;
==Documentation==&lt;br /&gt;
&lt;br /&gt;
Help write and edit our [https://docs.moodle.org/overview/ user documentation in various languages] or our [[Main_Page|developer docs]].&lt;br /&gt;
&lt;br /&gt;
==Plugins==&lt;br /&gt;
&lt;br /&gt;
Have you developed a Moodle plugin? You can [[Mobile_support_for_plugins|adapt your plugin to the Moodle App]].&lt;br /&gt;
&lt;br /&gt;
==Development==&lt;br /&gt;
&lt;br /&gt;
Fix a bug or add a new feature in the Moodle App. The development process is similar to the Moodle development process.&lt;br /&gt;
&lt;br /&gt;
First you should [[Setting_up_your_development_environment_for_Moodle_Mobile_2|set up your development environment]]. Once your issue is ready you can use our [[Moodle_App_scripts:_gulp_push|gulp push script]] to easily add your changes to the Moodle Tracker.&lt;br /&gt;
&lt;br /&gt;
==Translation==&lt;br /&gt;
&lt;br /&gt;
Assist with the [[Translating_the_Moodle_app|translation of the Moodle App]].&lt;br /&gt;
&lt;br /&gt;
[[Category:Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_4.0.0_release_notes&amp;diff=62130</id>
		<title>Moodle App 4.0.0 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_4.0.0_release_notes&amp;diff=62130"/>
		<updated>2022-04-28T13:54:28Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Moodle App Release Notes|Moodle App release notes]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
Release date: 25 April 2022&lt;br /&gt;
&lt;br /&gt;
Please notice this new version will only connect Moodle sites version &amp;gt;= 3.5 and that it won&#039;t work if your site is using the deprecated local_mobile (Mobile app additional features) plugin.&lt;br /&gt;
==New features and improvements==&lt;br /&gt;
* Support Moodle 4.0 LMS new &amp;quot;My courses&amp;quot; page&lt;br /&gt;
* User profile repositioned to match LMS&lt;br /&gt;
* Design updated to Material design 3 matching overall Moodle style&lt;br /&gt;
* Some pages now support swipe (participants, book module, grades, forum discussions,...)&lt;br /&gt;
* Resuming a course or a book is now easier&lt;br /&gt;
* Course main page additional options menu redesigned&lt;br /&gt;
* Navigation between sections and activities improved&lt;br /&gt;
* SCORM, H5P automatic full-screen mode&lt;br /&gt;
* Thumbnails for videos are automatically generated&lt;br /&gt;
* Content and activities pages with fewer distractions for users so they can focus on learning&lt;br /&gt;
* General performance improvements&lt;br /&gt;
* New notifications page (capturing all the notifications received by the user)&lt;br /&gt;
* The enrolment fee plugin is now supported (launched in a browser outside the app to complete the process)&lt;br /&gt;
* New 4.0 assignment time-limited submissions supported&lt;br /&gt;
* Teachers can now see H5P attempt reports&lt;br /&gt;
==Complete list of issues==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Bug ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== New Feature ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Task ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Improvement ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sub-task ===&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Release notes]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_4.0.0_release_notes&amp;diff=62129</id>
		<title>Moodle App 4.0.0 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_4.0.0_release_notes&amp;diff=62129"/>
		<updated>2022-04-28T13:54:03Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Moodle App Release Notes|Moodle App release notes]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
Release date: 25 April 2022&lt;br /&gt;
==New features and improvements==&lt;br /&gt;
* Support Moodle 4.0 LMS new &amp;quot;My courses&amp;quot; page&lt;br /&gt;
* User profile repositioned to match LMS&lt;br /&gt;
* Design updated to Material design 3 matching overall Moodle style&lt;br /&gt;
* Some pages now support swipe (participants, book module, grades, forum discussions,...)&lt;br /&gt;
* Resuming a course or a book is now easier&lt;br /&gt;
* Course main page additional options menu redesigned&lt;br /&gt;
* Navigation between sections and activities improved&lt;br /&gt;
* SCORM, H5P automatic full-screen mode&lt;br /&gt;
* Thumbnails for videos are automatically generated&lt;br /&gt;
* Content and activities pages with fewer distractions for users so they can focus on learning&lt;br /&gt;
* General performance improvements&lt;br /&gt;
* New notifications page (capturing all the notifications received by the user)&lt;br /&gt;
* The enrolment fee plugin is now supported (launched in a browser outside the app to complete the process)&lt;br /&gt;
* New 4.0 assignment time-limited submissions supported&lt;br /&gt;
* Teachers can now see H5P attempt reports&lt;br /&gt;
==Complete list of issues==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Bug ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== New Feature ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Task ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Improvement ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sub-task ===&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Release notes]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_4.0.0_release_notes&amp;diff=62128</id>
		<title>Moodle App 4.0.0 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_4.0.0_release_notes&amp;diff=62128"/>
		<updated>2022-04-28T13:53:27Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: Created page with &amp;quot;Moodle Mobile release notes &amp;gt; {{FULLPAGENAME}}  Release date: 25 April 2022 ==New features and improvements== * Support Moodle 4.0 LMS new &amp;quot;My courses&amp;quot; page * User profile...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Moodle Mobile release notes]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
Release date: 25 April 2022&lt;br /&gt;
==New features and improvements==&lt;br /&gt;
* Support Moodle 4.0 LMS new &amp;quot;My courses&amp;quot; page&lt;br /&gt;
* User profile repositioned to match LMS&lt;br /&gt;
* Design updated to Material design 3 matching overall Moodle style&lt;br /&gt;
* Some pages now support swipe (participants, book module, grades, forum discussions,...)&lt;br /&gt;
* Resuming a course or a book is now easier&lt;br /&gt;
* Course main page additional options menu redesigned&lt;br /&gt;
* Navigation between sections and activities improved&lt;br /&gt;
* SCORM, H5P automatic full-screen mode&lt;br /&gt;
* Thumbnails for videos are automatically generated&lt;br /&gt;
* Content and activities pages with fewer distractions for users so they can focus on learning&lt;br /&gt;
* General performance improvements&lt;br /&gt;
* New notifications page (capturing all the notifications received by the user)&lt;br /&gt;
* The enrolment fee plugin is now supported (launched in a browser outside the app to complete the process)&lt;br /&gt;
* New 4.0 assignment time-limited submissions supported&lt;br /&gt;
* Teachers can now see H5P attempt reports&lt;br /&gt;
&lt;br /&gt;
==Complete list of issues==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Bug ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== New Feature ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Task ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Improvement ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Sub-task ===&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Release notes]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Release_Notes&amp;diff=62127</id>
		<title>Moodle App Release Notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_Release_Notes&amp;diff=62127"/>
		<updated>2022-04-28T13:51:31Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Moodle App ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Version name&lt;br /&gt;
! Date&lt;br /&gt;
! Version code&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
!Moodle App 4.0.0&lt;br /&gt;
|25 April 2022&lt;br /&gt;
|40000&lt;br /&gt;
|[[Moodle App 4.0.0 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.9.5&lt;br /&gt;
| 27 August 2021&lt;br /&gt;
| 39500&lt;br /&gt;
| [[Moodle Mobile 3.9.5 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.9.4&lt;br /&gt;
| 28 December 2020&lt;br /&gt;
| 39400&lt;br /&gt;
|[[Moodle Mobile 3.9.4 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.9.3&lt;br /&gt;
| 30 November 2020&lt;br /&gt;
| 39300&lt;br /&gt;
| [[Moodle Mobile 3.9.3 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.9.2&lt;br /&gt;
| 16 July 2020&lt;br /&gt;
| 39200&lt;br /&gt;
| [[Moodle Mobile 3.9.2 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.9.1&lt;br /&gt;
| 3 July 2020&lt;br /&gt;
| 39100&lt;br /&gt;
| [[Moodle Mobile 3.9.1 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.9.0&lt;br /&gt;
| 26 June 2020&lt;br /&gt;
| 39000&lt;br /&gt;
| [[Moodle Mobile 3.9.0 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.8.2&lt;br /&gt;
| 2 April 2020&lt;br /&gt;
| 38200&lt;br /&gt;
| [[Moodle Mobile 3.8.2 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.8.1&lt;br /&gt;
| 25 March 2020&lt;br /&gt;
| 38100&lt;br /&gt;
| [[Moodle Mobile 3.8.1 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.8.0&lt;br /&gt;
| 20 December 2019&lt;br /&gt;
| 38000&lt;br /&gt;
| [[Moodle Mobile 3.8.0 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.7.2&lt;br /&gt;
| 20 September 2019&lt;br /&gt;
| 37200&lt;br /&gt;
| [[Moodle Mobile 3.7.2 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.7.1&lt;br /&gt;
| 30 August 2019&lt;br /&gt;
| 37100&lt;br /&gt;
| [[Moodle Mobile 3.7.1 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.7.0&lt;br /&gt;
| 7 June 2019&lt;br /&gt;
| 37000&lt;br /&gt;
| [[Moodle Mobile 3.7.0 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.6.1&lt;br /&gt;
| 29 March 2019&lt;br /&gt;
| 36100&lt;br /&gt;
| [[Moodle Mobile 3.6.1 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.6.0&lt;br /&gt;
| 11 January 2019&lt;br /&gt;
| 36000&lt;br /&gt;
| [[Moodle Mobile 3.6.0 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.5.2&lt;br /&gt;
| 10 September 2018&lt;br /&gt;
| 35200&lt;br /&gt;
| [[Moodle Mobile 3.5.2 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.5.1&lt;br /&gt;
| 6 July 2018&lt;br /&gt;
| 35100&lt;br /&gt;
| [[Moodle Mobile 3.5.1 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle App 3.5.0&lt;br /&gt;
| 25 June 2018&lt;br /&gt;
| 35000&lt;br /&gt;
| [[Moodle Mobile 3.5.0 release notes|Release Notes]]&lt;br /&gt;
|}&lt;br /&gt;
== Moodle Mobile ==&lt;br /&gt;
Versions with support or 3.0 and previous versions, based on Ionic 1.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Version name&lt;br /&gt;
! Date&lt;br /&gt;
! Version code&lt;br /&gt;
!&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.4.2&lt;br /&gt;
| 19 March 2018&lt;br /&gt;
| 20230&lt;br /&gt;
| [[Moodle Mobile 3.4.2 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.4.1&lt;br /&gt;
| 28 February 2018&lt;br /&gt;
| 20220&lt;br /&gt;
| [[Moodle Mobile 3.4.1 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.4.0&lt;br /&gt;
| 30 November 2017&lt;br /&gt;
| 20210&lt;br /&gt;
| [[Moodle Mobile 3.4.0 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.3.2&lt;br /&gt;
| 29 September 2017&lt;br /&gt;
| 20200&lt;br /&gt;
| [[Moodle Mobile 3.3.2 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.3.1&lt;br /&gt;
| 28 July 2017&lt;br /&gt;
| 20190&lt;br /&gt;
| [[Moodle Mobile 3.3.1 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.3.0&lt;br /&gt;
| 26 May 2017&lt;br /&gt;
| 20180&lt;br /&gt;
| [[Moodle Mobile 3.3.0 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.2.1&lt;br /&gt;
| 28 February 2017&lt;br /&gt;
| 20170&lt;br /&gt;
| [[Moodle Mobile 3.2.1 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.2.0&lt;br /&gt;
| 12 December 2016&lt;br /&gt;
| 20160&lt;br /&gt;
| [[Moodle Mobile 3.2.0 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.1.3&lt;br /&gt;
| 19 October 2016&lt;br /&gt;
| 20150&lt;br /&gt;
| [[Moodle Mobile 3.1.3 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.1.2&lt;br /&gt;
| 27 July 2016&lt;br /&gt;
| 2014&lt;br /&gt;
| [[Moodle Mobile 3.1.2 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.1.1&lt;br /&gt;
| 14 June 2016&lt;br /&gt;
| 2013&lt;br /&gt;
| [[Moodle Mobile 3.1.1 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.1.0&lt;br /&gt;
| 23 May 2016&lt;br /&gt;
| 2012&lt;br /&gt;
| [[Moodle Mobile 3.1.0 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 3.0.0&lt;br /&gt;
| 15 April 2016&lt;br /&gt;
| 2011&lt;br /&gt;
| [[Moodle Mobile 3.0.0 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 2.9&lt;br /&gt;
| 29 February 2016&lt;br /&gt;
| 2010&lt;br /&gt;
| [[Moodle Mobile 2.9 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 2.8&lt;br /&gt;
| 22 January 2016&lt;br /&gt;
| 2009&lt;br /&gt;
| [[Moodle Mobile 2.8 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 2.7&lt;br /&gt;
| 29 December 2015&lt;br /&gt;
| 2008&lt;br /&gt;
| [[Moodle Mobile 2.7 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 2.6&lt;br /&gt;
| 27 November 2015&lt;br /&gt;
| 2007&lt;br /&gt;
| [[Moodle Mobile 2.6 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 2.5&lt;br /&gt;
| 6 November 2015&lt;br /&gt;
| 2006&lt;br /&gt;
| [[Moodle Mobile 2.5 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 2.4&lt;br /&gt;
| 21 October 2015&lt;br /&gt;
| 2005&lt;br /&gt;
| [[Moodle Mobile 2.4 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 2.3&lt;br /&gt;
| 8 October 2015&lt;br /&gt;
| 2004&lt;br /&gt;
| [[Moodle Mobile 2.3 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 2.2&lt;br /&gt;
| 10 September 2015&lt;br /&gt;
| 2003&lt;br /&gt;
| [[Moodle Mobile 2.2 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 2.1&lt;br /&gt;
| 28 August 2015&lt;br /&gt;
| 2002&lt;br /&gt;
| [[Moodle Mobile 2.1 release notes|Release Notes]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Mobile 2.0&lt;br /&gt;
| 31 July 2015&lt;br /&gt;
| 2000&lt;br /&gt;
| [[Moodle Mobile 2.0 release notes|Release Notes]]&lt;br /&gt;
|}&lt;br /&gt;
== Moodle Mobile 1 ==&lt;br /&gt;
See [[Moodle Mobile 1 release notes]]&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Moodle Desktop release notes]]&lt;br /&gt;
* [[Moodle Mobile Roadmap]]&lt;br /&gt;
[[Category: Mobile]]&lt;br /&gt;
[[Category:Release notes]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Contributing_to_the_Moodle_app&amp;diff=62081</id>
		<title>Contributing to the Moodle app</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Contributing_to_the_Moodle_app&amp;diff=62081"/>
		<updated>2022-04-26T15:23:25Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;There are lots of ways you can contribute to the Moodle App project. If you want to see more ways to contribute to the Moodle project, please see [[Contributing_to_Moodle|Contributing to Moodle]].&lt;br /&gt;
&lt;br /&gt;
==User support==&lt;br /&gt;
&lt;br /&gt;
Join in with the forum discussions in [https://moodle.org/mod/forum/view.php?id=7798 Moodle for Mobile forum].&lt;br /&gt;
&lt;br /&gt;
==Documentation==&lt;br /&gt;
&lt;br /&gt;
Help write and edit our [https://docs.moodle.org/overview/ user documentation in various languages] or our [[Main_Page|developer docs]].&lt;br /&gt;
&lt;br /&gt;
==Plugins==&lt;br /&gt;
&lt;br /&gt;
Have you developed a Moodle plugin? You can [[Mobile_support_for_plugins|adapt your plugin to the Moodle App]].&lt;br /&gt;
&lt;br /&gt;
==Development==&lt;br /&gt;
&lt;br /&gt;
Fix a bug or add a new feature in the Moodle App. The development process is similar to the Moodle development process.&lt;br /&gt;
&lt;br /&gt;
First you should [[Setting_up_your_development_environment_for_Moodle_Mobile_2|set up your development environment]]. Once your issue is ready you can use our [[Moodle_App_scripts:_gulp_push|gulp push script]] to easily add your changes to the Moodle Tracker.&lt;br /&gt;
&lt;br /&gt;
==Translation==&lt;br /&gt;
&lt;br /&gt;
Assist with the [[Translating_the_Moodle_app|translation of the Moodle App]].&lt;br /&gt;
&lt;br /&gt;
[[Category:Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Desktop_release_notes&amp;diff=62064</id>
		<title>Moodle Desktop release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Desktop_release_notes&amp;diff=62064"/>
		<updated>2022-04-25T11:02:23Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;Note: [https://moodle.org/mod/forum/discuss.php?d=413350 Moodle Desktop is discontinued] since 1 May 2021.&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Version name&lt;br /&gt;
! Date&lt;br /&gt;
! Platforms&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.7.2&lt;br /&gt;
| 20 September 2019&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.7.2_release_notes|Moodle Mobile 3.7.2]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.7.1&lt;br /&gt;
| -&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| Version skipped due to a security issue detected in Moodle App 3.7.1&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.7.0&lt;br /&gt;
| 12 Julio 2019&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.7.0_release_notes|Moodle Mobile 3.7.0]]&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.6.1&lt;br /&gt;
| 14 May 2019&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.6.1_release_notes|Moodle Mobile 3.6.1]] plus the following desktop-specific improvement:&lt;br /&gt;
* It is now possible to enable the &amp;quot;Developer Tools&amp;quot; in the Desktop app using the same key combinations that the Chrome browser: &amp;quot;Control + Shift + I&amp;quot; (in MacOs is Command + Option + I).&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.6.0&lt;br /&gt;
| 3 February 2019&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.6.0_release_notes|Moodle Mobile 3.6.0]] &lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.5.2&lt;br /&gt;
| 19 September 2018&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.5.2_release_notes|Moodle Mobile 3.5.2]] &lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.5.1&lt;br /&gt;
| 25 July 2018&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.5.1_release_notes|Moodle Mobile 3.5.1]] &lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.5.0&lt;br /&gt;
| -&lt;br /&gt;
| &lt;br /&gt;
-&lt;br /&gt;
| Skipped, see Moodle Desktop 3.5.1&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.4.2&lt;br /&gt;
| 20 March 2018&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.4.2_release_notes|Moodle Mobile 3.4.2]] &lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.4.1&lt;br /&gt;
| 1 March 2018&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.4.1_release_notes|Moodle Mobile 3.4.1]] &lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.4.0&lt;br /&gt;
| 1 December 2017&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.4.0_release_notes|Moodle Mobile 3.4.0]] &lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.3.2&lt;br /&gt;
| 29 September 2017&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux (32 and 64 bits)&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.3.2_release_notes|Moodle Mobile 3.3.2]] plus:&lt;br /&gt;
* New 32 bits version for Linux&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.3.1&lt;br /&gt;
| 8 August 2017&lt;br /&gt;
| &lt;br /&gt;
* Windows 7, 8 and 10&lt;br /&gt;
* macOS&lt;br /&gt;
* Linux&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.3.1_release_notes|Moodle Mobile 3.3.1]] plus the following desktop-specific improvements:&lt;br /&gt;
* The app keeps the focus when re-opened via the status bar&lt;br /&gt;
* Scroll down on pages using a scroll wheel or trackpad gesture is now supported&lt;br /&gt;
|-&lt;br /&gt;
! Moodle Desktop 3.3.0&lt;br /&gt;
| 5 July 2017&lt;br /&gt;
| Windows 10&lt;br /&gt;
| With all the features of [[Moodle_Mobile_3.3.0_release_notes|Moodle Mobile 3.3.0]]&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[:en:Moodle Desktop|Moodle Desktop user documentation]]&lt;br /&gt;
* [[Moodle Mobile release notes]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_release_notes&amp;diff=62063</id>
		<title>Moodle Mobile 1 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_release_notes&amp;diff=62063"/>
		<updated>2022-04-25T11:01:32Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Moodle Mobile release notes]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
=== Moodle Mobile 1.15 ===&lt;br /&gt;
29th May 2015&lt;br /&gt;
&lt;br /&gt;
This will be the last release of the Moodle Mobile 1.x series.&lt;br /&gt;
&lt;br /&gt;
Fixes and improvements:&lt;br /&gt;
* Prevent the user to log-in when file downloading is disabled in the Mobile service&lt;br /&gt;
* Content is now correctly displayed in Android 4.4.2&lt;br /&gt;
* The Moodle Mobile additional features plugin has been updated to support Moodle 2.9&lt;br /&gt;
* Prevent to process Moodle filters js code when rendering panels&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.15%22 Complete list of fixed issues in Moodle Mobile 1.15]&lt;br /&gt;
=== Moodle Mobile 1.14 ===&lt;br /&gt;
30th April 2015&lt;br /&gt;
&lt;br /&gt;
New features and improvements:&lt;br /&gt;
* Student activity is showed in the Moodle site logs (requires Moodle 2.7 and onwards with the local_mobile plugin)&lt;br /&gt;
* Content in the app is processed through Moodle filters&lt;br /&gt;
* App updated to use new features and improvements in Moodle 2.9&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.14%22 Complete list of fixed issues in Moodle Mobile 1.14]&lt;br /&gt;
=== Moodle Mobile 1.13 ===&lt;br /&gt;
27th March 2015&lt;br /&gt;
&lt;br /&gt;
New features:&lt;br /&gt;
* Teachers can view student&#039;s assignment submissions (both files and online-text) online and offline. (Requires Moodle 2.7)&lt;br /&gt;
* Files are opened in iOs using the Quick Look framework; from there they can be shared, printed or opened with a different application.&lt;br /&gt;
Improvements:&lt;br /&gt;
* The topic list for the front page now displays only one section.&lt;br /&gt;
Fixes:&lt;br /&gt;
* Javascript time function does not recognize some locales&lt;br /&gt;
* When scrolling down the contents page, some elements like pages or URLs are opened&lt;br /&gt;
* Incorrect function listed in the local_mobile db/services.php&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.13%22 Complete list of fixed issues in Moodle Mobile 1.13]&lt;br /&gt;
=== Moodle Mobile 1.12 ===&lt;br /&gt;
20th February 2015&lt;br /&gt;
&lt;br /&gt;
New features:&lt;br /&gt;
* Any type of file can be uploaded to Moodle (in iOs using the &amp;quot;Open with&amp;quot; option; in Android via a new option in the Upload section)&lt;br /&gt;
* Group events are now displayed in the events list&lt;br /&gt;
* New reminder options for local notifications&lt;br /&gt;
* Removed unused permissions in Android; the app asks for less permissions during installation (yay!)&lt;br /&gt;
* Blocked users are listed in the contacts page&lt;br /&gt;
* You can block/unblock users in the messages option&lt;br /&gt;
Improvements:&lt;br /&gt;
* Local notifications: better synchronization with calendar events&lt;br /&gt;
* Notifications title in Android is now the site name (instead the Moodle Mobile word)&lt;br /&gt;
* Front page course is now supported&lt;br /&gt;
* When a site is deleted, the device is unregistered in your site&lt;br /&gt;
* Report a bug via email and &amp;quot;add as contact&amp;quot; options replaced&lt;br /&gt;
* Added extra debugging in order to warn developers that their Moodle custom version uses deprecated Web Services&lt;br /&gt;
* Documentation improved (general documentation, notifications, etc..)&lt;br /&gt;
* App description translated to new languages&lt;br /&gt;
Fixes:&lt;br /&gt;
* Fixed Iframe scrolling problems under iOS&lt;br /&gt;
* Fixed files not being deleted after being uploaded (video, audio and image)&lt;br /&gt;
* Fixed NW.js isFile missing function&lt;br /&gt;
* Fixed problem with uploading audio (NW.js version)&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.12%22 Complete list of fixed issues in Moodle Mobile 1.12]&lt;br /&gt;
=== Moodle Mobile 1.11 ===&lt;br /&gt;
16th January 2015&lt;br /&gt;
&lt;br /&gt;
New features:&lt;br /&gt;
* Calendar integration with warning reminders as local notifications&lt;br /&gt;
* Sections summary is displayed in the course contents page&lt;br /&gt;
Improvements:&lt;br /&gt;
* The app uses non-deprecated web services if available&lt;br /&gt;
* Countries full name are displayed instead international short code&lt;br /&gt;
Fixed:&lt;br /&gt;
* Scrolling in the forum discussion screen open posts&lt;br /&gt;
* Page and resource contents opened inside an iframe don&#039;t open external links&lt;br /&gt;
* Resources with multiples files not available in offline mode (regression)&lt;br /&gt;
* New message text box incorrectly positioned&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.11%22 Complete list of fixed issues in Moodle Mobile 1.11]&lt;br /&gt;
=== Moodle Mobile 1.10 ===&lt;br /&gt;
26th December 2014&lt;br /&gt;
&lt;br /&gt;
New features:&lt;br /&gt;
* Messages: new interface similar to Whatsapp/Telegram. You can view your recent conversations, search for contacts, add/remove contacts and view user profiles.&lt;br /&gt;
Improvements:&lt;br /&gt;
* New navigation system with improved headers. The main menu and submenus are now more consistent.&lt;br /&gt;
* Synchronization: You can choose to enable sync data only when on Wifi connection.&lt;br /&gt;
Fixes:&lt;br /&gt;
* Removing a site should delete all the entries in the database related to that site&lt;br /&gt;
* The user submenu styles has hard-coded colors&lt;br /&gt;
* App icon has large black outline on iOS8&lt;br /&gt;
* If a URL without the preceding www is specified then you get a very unhelpful error message&lt;br /&gt;
* Styling not applied when no notifications seen&lt;br /&gt;
* Label texts are shorten not truncating the last word&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.10%22 Complete list of fixed issues in Moodle Mobile 1.10]&lt;br /&gt;
=== Moodle Mobile 1.9 ===&lt;br /&gt;
28th November 2014&lt;br /&gt;
&lt;br /&gt;
New features:&lt;br /&gt;
* New languages added: Bulgarian, Turkish and Chinese traditional&lt;br /&gt;
* Notifications: Forum notifications are more concise and include a link to open the discussion directly in the app.&lt;br /&gt;
* Grades: New &amp;quot;Show grades&amp;quot; button in the participants profile page to view that participant&#039;s grades (if you are allowed)&lt;br /&gt;
Improvements:&lt;br /&gt;
* General design: New main menu and back icons, courses menu section improved design.&lt;br /&gt;
* Forum design and usability: Discussions are now opened in a new page displaying all the posts expanded. Forum notifications open the discussion inside the app. Pressing the user name opens the user information page.&lt;br /&gt;
* Grades design and usability: Added missing icons, fixed background colours.&lt;br /&gt;
* Recent messages list design&lt;br /&gt;
Fixes:&lt;br /&gt;
* A course with empty folders displays errors&lt;br /&gt;
* The Mobile notifications server is smarter when using the smallmessage part of the notification&lt;br /&gt;
* A notification when no site is active will display a warning&lt;br /&gt;
* Users without permissions to view grades can&#039;t access the grades option&lt;br /&gt;
* Refresh button updates the current_site cache correctly&lt;br /&gt;
* Message sending blocks popups properly&lt;br /&gt;
* Labels doesn&#039;t display text properly filtered (TeX support missing)&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.9%22 Complete list of fixed issues in Moodle Mobile 1.9]&lt;br /&gt;
=== Moodle Mobile 1.8 ===&lt;br /&gt;
10th November 2014&lt;br /&gt;
&lt;br /&gt;
New features:&lt;br /&gt;
* Support for Moodle 2.8&lt;br /&gt;
* Messages and notifications are separated in two different views&lt;br /&gt;
* HTML and Page resources are displayed using a viewer inside the app.&lt;br /&gt;
* If the user presses a phone notification, the app is opened displaying the complete notification&lt;br /&gt;
* Notification title is displayed in Android notifications bar&lt;br /&gt;
Improvements:&lt;br /&gt;
* Category and manual items are now displayed in the grades option&lt;br /&gt;
* Participants list is now paginated&lt;br /&gt;
* Downloaded files are opened automatically&lt;br /&gt;
* Downloaded files are named exactly as Moodle had them with UTF-8, white spaces etc.&lt;br /&gt;
* Keyboard is automatically displayed when a text field gains focus&lt;br /&gt;
Fixes:&lt;br /&gt;
* Interval time now decreases if a hook has a interval time lower than the defined in the app&lt;br /&gt;
* DirectorySize function now copes with empty subdirectories&lt;br /&gt;
Other:&lt;br /&gt;
* Donate to Moodle and help it grow! https://moodle.org/donations&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.8%22 Complete list of fixed issues in Moodle Mobile 1.8]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Moodle Mobile 1.7 ===&lt;br /&gt;
10th October 2014&lt;br /&gt;
&lt;br /&gt;
New features:&lt;br /&gt;
* Forum activity support - users can read posts on and off-line, with messages synchronized periodically from the server (Note: requires the [[:en:Moodle_Mobile_additional_features|Moodle Mobile additional features add-on]])&lt;br /&gt;
* All files in a directory, page or file resource can be downloaded in just one tap&lt;br /&gt;
* Language translations updated&lt;br /&gt;
* New language: Persian&lt;br /&gt;
Improvements:&lt;br /&gt;
* Upgraded Cordova lib to 3.6.1&lt;br /&gt;
* New icon for activities only viewable in the Moodle site&lt;br /&gt;
* New loading icons&lt;br /&gt;
* In the calendar the panel on the right is not displayed in tablet view when there is no event&lt;br /&gt;
* Improved animation performance&lt;br /&gt;
Fixes:&lt;br /&gt;
* Complete support for iOS 8 devices&lt;br /&gt;
* Files inside folders in resources and directories are correctly downloaded&lt;br /&gt;
* Synchronization hooks for plugins fixed&lt;br /&gt;
* File names with German umlauts are correctly downloaded in the &amp;quot;My files&amp;quot; plugin&lt;br /&gt;
* Images are correctly stored for offline display in course labels&lt;br /&gt;
* File info pop-up is correctly displayed in folder and multiple files resources view&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.7%22 Complete list of fixed issues in Moodle Mobile 1.7]&lt;br /&gt;
=== Moodle Mobile 1.6.1 ===&lt;br /&gt;
8th September 2014 (Android, iOS, WP8, W8)&lt;br /&gt;
&lt;br /&gt;
New features:&lt;br /&gt;
* Language translations updated&lt;br /&gt;
* New languages: Japanese, Russian and Hebrew&lt;br /&gt;
Improvements:&lt;br /&gt;
* Page titles displayed in “Tablet” view&lt;br /&gt;
* URL resources open in new browser (without requiring Moodle login)&lt;br /&gt;
* Files and resource contents visualization&lt;br /&gt;
Fixes:&lt;br /&gt;
* Invalid check of passwords incorrectly trimmed&lt;br /&gt;
* Files with spaces and German umlauts can’t be downloaded&lt;br /&gt;
* Login errors in sites nor returning release information&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.6.1%22 Complete list of fixed issues in Moodle Mobile 1.6.1]&lt;br /&gt;
=== Moodle Mobile 1.6 ===&lt;br /&gt;
7th August 2014 (Android, iOS, WP8, W8)&lt;br /&gt;
&lt;br /&gt;
New features:&lt;br /&gt;
* View your total grade for each course&lt;br /&gt;
* New “My files” feature for browsing and downloading your private and course files&lt;br /&gt;
* New collapsible “Courses” menu for browsing courses&lt;br /&gt;
Improvements:&lt;br /&gt;
* Design polishing and optimization (notifications, calendar events and settings page)&lt;br /&gt;
* Grade retrieval optimized (faster with latest version of local_mobile plugin)&lt;br /&gt;
* Grade error handling improved&lt;br /&gt;
Fixes:&lt;br /&gt;
* Fixed display errors when browsing some types of activities&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.6%22 Complete list of fixed issues in Moodle Mobile 1.6]&lt;br /&gt;
=== Moodle Mobile 1.5 ===&lt;br /&gt;
18th July 2014 (Android and iOS)&lt;br /&gt;
&lt;br /&gt;
New features:&lt;br /&gt;
* View your activity grades and outcomes (Requires configuration by the Moodle site administrator)&lt;br /&gt;
* New sign in and manage accounts page, quick access to all your sites&lt;br /&gt;
Improvements: &lt;br /&gt;
* The Site URL field now supports domains without the http/https protocol prefix&lt;br /&gt;
* Notifications page design improved&lt;br /&gt;
* Messages received can be replied in-line&lt;br /&gt;
* Refresh now updates all information related to the site&lt;br /&gt;
* The help link in the menu now points to documentation matching the current site version&lt;br /&gt;
* App design polished and optimized, improved support for site themes&lt;br /&gt;
Fixes:&lt;br /&gt;
* Menu scrolling bad positioning issue fixed&lt;br /&gt;
* Upload options are only available now if the service has the “Allow upload of files” setting enabled (for Moodle 2.6 and onwards)&lt;br /&gt;
Information for developers:&lt;br /&gt;
* New [[Moodle Mobile simulator]] for easier development&lt;br /&gt;
* New support for the Phonegap Developer App: [[Moodle Mobile Testing with Phonegap Developer app]]&lt;br /&gt;
* Themes creation documentation: [[Moodle Mobile Themes]]&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.5%22 Complete list of fixed issues in Moodle Mobile 1.5]&lt;br /&gt;
=== Moodle Mobile 1.4.4 ===&lt;br /&gt;
12th June 2014 (Android and iOS)&lt;br /&gt;
&lt;br /&gt;
Added support for Moodle sites using SSO authentication methods like CAS or Shibboleth&lt;br /&gt;
&lt;br /&gt;
Added support for the [[:en:Moodle_Mobile_additional_features|Moodle Mobile additional features add-on]]. Using that service all the app features are available for Moodle versions starting 2.4 (Including Push notifications)&lt;br /&gt;
&lt;br /&gt;
New feature: Private messages and notifications retrieval&lt;br /&gt;
&lt;br /&gt;
New language: Traditional Chinese&lt;br /&gt;
&lt;br /&gt;
Relaxed validation rules for URLs so Moodle sites under private/internal networks can work&lt;br /&gt;
&lt;br /&gt;
Improved support for remote styles&lt;br /&gt;
&lt;br /&gt;
Improved offline and synchronization features&lt;br /&gt;
&lt;br /&gt;
Fixed problem with photo uploading (it didn’t work correctly for some sites)&lt;br /&gt;
&lt;br /&gt;
Fixed problem with settings plugins not begin displayed correctly in the settings menu&lt;br /&gt;
&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.4.4%22 Complete list of fixed issues in Moodle Mobile 1.4.4]&lt;br /&gt;
=== Moodle Mobile 1.4.3 ===&lt;br /&gt;
12th May 2014 (Android and iOS)&lt;br /&gt;
&lt;br /&gt;
Android only: Added support for Push notifications (requires Moodle 2.7)&lt;br /&gt;
&lt;br /&gt;
iOs only: Push notifications improved&lt;br /&gt;
&lt;br /&gt;
New language: Arabic&lt;br /&gt;
&lt;br /&gt;
German language updated&lt;br /&gt;
&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.4.3%22 Complete list of fixed issues in Moodle Mobile 1.4.3]&lt;br /&gt;
=== Moodle Mobile 1.4.2 ===&lt;br /&gt;
1st May 2014 (Android and iOS)&lt;br /&gt;
&lt;br /&gt;
New language: German &lt;br /&gt;
&lt;br /&gt;
Improved: Reduced the app size &lt;br /&gt;
&lt;br /&gt;
Improved: Authentication security &lt;br /&gt;
&lt;br /&gt;
Fixed: The app doesn&#039;t overlay the status bar any more &lt;br /&gt;
&lt;br /&gt;
Fixed: Errors without proper information not displayed any more &lt;br /&gt;
&lt;br /&gt;
Fixed: The app supports different locales&lt;br /&gt;
&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.4.2%22 Complete list of fixed issues in Moodle Mobile 1.4.2]&lt;br /&gt;
=== Moodle Mobile 1.4.1 ===&lt;br /&gt;
10th April 2014 (Android and iOS)&lt;br /&gt;
&lt;br /&gt;
New feature: Video record and upload to your private files area in Moodle&lt;br /&gt;
&lt;br /&gt;
Improved: Error and exception handling (no more white screens)&lt;br /&gt;
&lt;br /&gt;
Fixed: Labels with images are correctly displayed &lt;br /&gt;
&lt;br /&gt;
Fixed: The app can download files from nginx servers&lt;br /&gt;
&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.4.1%22 Complete list of fixed issues in Moodle Mobile 1.4.1]&lt;br /&gt;
=== Moodle Mobile 1.4 ===&lt;br /&gt;
3rd March 2014 (Android and iOS)&lt;br /&gt;
&lt;br /&gt;
Notifications (iOS only): Supported by any Moodle 2.7 site or Moodle 2.6 with optional messaging output plugin&lt;br /&gt;
&lt;br /&gt;
Calendar events (require Moodle 2.5.5 or Moodle 2.6.2 and onwards)&lt;br /&gt;
&lt;br /&gt;
Alert when downloading too big files&lt;br /&gt;
&lt;br /&gt;
Several bug fixes&lt;br /&gt;
&lt;br /&gt;
New translations available:&lt;br /&gt;
* Mexican Spanish&lt;br /&gt;
* Euskara&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.4%22 Complete list of fixed issues in Moodle Mobile 1.4]&lt;br /&gt;
=== Moodle Mobile 1.3.4 ===&lt;br /&gt;
4th February 2014 (Android and iOS)&lt;br /&gt;
&lt;br /&gt;
Contents/resources can be re-downloaded at any time&lt;br /&gt;
&lt;br /&gt;
Application translated to these languages:&lt;br /&gt;
* Catalan&lt;br /&gt;
* Czech&lt;br /&gt;
* Spanish&lt;br /&gt;
* French&lt;br /&gt;
* Italian&lt;br /&gt;
* Hungarian&lt;br /&gt;
* Dutch&lt;br /&gt;
* Swedish&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=fixVersion%20%3D%20%221.3.4%22%20AND%20project%20%3D%20MOBILE Complete list of fixed issues in Moodle Mobile 1.3.4]&lt;br /&gt;
=== Moodle Mobile 1.3.3 ===&lt;br /&gt;
22th January (Android and iOS)&lt;br /&gt;
* New &amp;quot;About&amp;quot; option in Settings&lt;br /&gt;
&lt;br /&gt;
* New &amp;quot;Space usage&amp;quot; option in Settings&lt;br /&gt;
&lt;br /&gt;
* Initial support for node-webkit&lt;br /&gt;
&lt;br /&gt;
* Fixed bug that broke the app in Android 4.4&lt;br /&gt;
&lt;br /&gt;
* Fixed scrolling bug in contents and participants section for Android 2.1, 2.2 and 2.3&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=fixVersion%20%3D%20%221.3.3%22%20AND%20project%20%3D%20MOBILE Complete list of fixed issues in Moodle Mobile 1.3.3]&lt;br /&gt;
=== Moodle Mobile 1.3.2 ===&lt;br /&gt;
12th December (Android and iOS)&lt;br /&gt;
* Fixed bug that prevents files with white spaces in their name to be displayed&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20fixVersion%20%3D%20%221.3.2%22%20AND%20status%20%3D%20Open%20ORDER%20BY%20priority%20DESC Complete list of fixed issues in Moodle Mobile 1.3.2]&lt;br /&gt;
=== Moodle Mobile 1.3.1 ===&lt;br /&gt;
1st October (Android and iOS)&lt;br /&gt;
* Fixed the problem related to force log-in every time they opened the app (Android only)&lt;br /&gt;
* New quick logout option&lt;br /&gt;
* New splash screen&lt;br /&gt;
* Small fixes related to design&lt;br /&gt;
* Security fixes related to sites with certificates&lt;br /&gt;
[https://tracker.moodle.org/issues/?jql=fixVersion%20%3D%20%221.3.1%22%20AND%20project%20%3D%20MOBILE Complete list of fixed issues in Moodle Mobile 1.3.1]&lt;br /&gt;
=== Moodle Mobile 1.3 ===&lt;br /&gt;
24th September (Android and iOS)&lt;br /&gt;
&lt;br /&gt;
[[Moodle Mobile 1.3 Release Notes]]&lt;br /&gt;
=== Moodle Mobile 1.2.2 ===&lt;br /&gt;
27 May 2013 Android&lt;br /&gt;
&lt;br /&gt;
12 September 2013 iOS&lt;br /&gt;
&lt;br /&gt;
Security release:&lt;br /&gt;
* MOBILE-413 bug fixed&lt;br /&gt;
&lt;br /&gt;
* Improved security of user tokens&lt;br /&gt;
=== Moodle Mobile 1.2.1 ===&lt;br /&gt;
15 April 2013 iOS&lt;br /&gt;
&lt;br /&gt;
8 April 2013 Android&lt;br /&gt;
&lt;br /&gt;
We’ve called it version 1.2 because the feature set is inherited from the deprecated iOS-only app [[Mobile app | “My Moodle”]] which was version 1.1.&lt;br /&gt;
&lt;br /&gt;
Thanks to all the developers who’ve worked on this version of the app: Juan Leyva, Jerome Mouneyrac, Barbara Ramiro, Martin Dougiamas and Josep Gifreu.&lt;br /&gt;
&lt;br /&gt;
The new app has been designed as a platform with the following strengths:&lt;br /&gt;
* It is secure &lt;br /&gt;
* It can work offline &lt;br /&gt;
* It can support notifications (coming soon)&lt;br /&gt;
* It can be branded and expanded by an institution&lt;br /&gt;
* It makes some Moodle operations faster and more convenient&lt;br /&gt;
&#039;&#039;&#039; Features &#039;&#039;&#039;&lt;br /&gt;
* Responsive design for phone and tablets&lt;br /&gt;
* Upload a picture into your private file area&lt;br /&gt;
* Record an audio file and upload it into your private file area&lt;br /&gt;
* Send a private message to a course participant (can be done offline)&lt;br /&gt;
* Take a personal note about a course participant (can be done offline)&lt;br /&gt;
* Add a course participant to your phone contact&lt;br /&gt;
* Call a course participant touching the phone number&lt;br /&gt;
* Locate a course participant address on Google map&lt;br /&gt;
* Download and view some resources&lt;br /&gt;
* Quick access to your course contents&lt;br /&gt;
* Remote translation (see bellow)&lt;br /&gt;
* Remote layout/style customization (see bellow)&lt;br /&gt;
&#039;&#039;&#039; Documentation &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
[[:en:Moodle Mobile app|Moodle Mobile app]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039; Requirements &#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Moodle 2.4&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Download links:&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Apps are available in Google Play and also Apple Store. You can install the app directly from your Mobile device, search for &amp;quot;Moodle Mobile&amp;quot; the author/owner must be &amp;quot;Moodle Pty Ltd&amp;quot;&lt;br /&gt;
* Android: https://play.google.com/store/apps/details?id=com.moodle.moodlemobile&lt;br /&gt;
&lt;br /&gt;
* iOS: https://itunes.apple.com/es/app/moodle-mobile/id633359593&lt;br /&gt;
&lt;br /&gt;
[[Category:Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62062</id>
		<title>Moodle App Plugins Development Guide</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62062"/>
		<updated>2022-04-25T08:31:03Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: /* core-format-text */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle App (Ionic 5)}}&lt;br /&gt;
If you want to add mobile support to your Moodle plugin, you can achieve it by extending different areas of the app using &#039;&#039;just PHP server side code&#039;&#039; and providing templates written with [https://ionicframework.com/docs/components Ionic] and custom components.&lt;br /&gt;
&lt;br /&gt;
You will have to:&lt;br /&gt;
# Create a &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file in your plugin. In this file, you will be able to indicate which areas of the app you want to extend. For example, adding a new option in the main menu, implementing support for a new activity module, including a new option in the course menu, including a new option in the user profile, etc. All the areas supported are described further in this document.&lt;br /&gt;
# Create new functions in a reserved namespace that will return the content of the new options. The content should be returned rendered as html. This html should use Ionic components so that it looks native, but it can be generated using mustache templates.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let’s clarify some points:&lt;br /&gt;
* You don’t need to create new Web Service functions (although you will be able to use them for advanced features). You just need plain php functions that will be placed in a reserved namespace.&lt;br /&gt;
* Those functions will be exported via the Web Service function &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt;.&lt;br /&gt;
* As arguments of your functions you will always receive the &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, some relevant details of the app (like the app version or the current language in the app), and some specific data depending on the type of plugin (&amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
* The mobile app also implements a list of custom Ionic components and directives that provide dynamic behaviour; like indicating that you are linking a file that can be downloaded, allowing a transition to new pages into the app calling a specific function in the server, submitting form data to the server, etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Getting started==&lt;br /&gt;
If you only want to write a plugin, it is not necessary that you set up your environment to work with the Moodle App. In fact, you don&#039;t even need to compile it. You can just [[Using the Moodle App in a browser|use a Chromium-based browser]] to add mobile support to your plugins!&lt;br /&gt;
&lt;br /&gt;
You can use the app from one of the hosted versions on [https://master.apps.moodledemo.net master.apps.moodledemo.net] (the latest stable version) and [https://integration.apps.moodledemo.net integration.apps.moodledemo.net] (the latest development version). If you need any specific environment (hosted versions are deployed with a &#039;&#039;production&#039;&#039; environment), you can also use [[Moodle App Docker Images|Docker images]]. And if you need to test your plugin in a native device, you can always use [https://download.moodle.org/mobile Moodle HQ&#039;s application].&lt;br /&gt;
&lt;br /&gt;
This should suffice for developing plugins. However, if you are working on advanced functionality and you need to run the application from the source code, you can find more information in the [[Moodle App Development Guide]].&lt;br /&gt;
===Development workflow===&lt;br /&gt;
Before getting into the specifics of your plugin, we recommend that you start adding a simple &amp;quot;Hello World&amp;quot; button in the app to see that everything works properly.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s say your plugin is called &amp;lt;code&amp;gt;local_hello&amp;lt;/code&amp;gt;, you can start by adding the following files:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;local_hello&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [&lt;br /&gt;
            &#039;hello&#039; =&amp;gt; [&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreMainMenuDelegate&#039;,&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;view_hello&#039;,&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;title&#039; =&amp;gt; &#039;hello&#039;,&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; &#039;earth&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [&lt;br /&gt;
            [&#039;hello&#039;, &#039;local_hello&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace local_hello\output;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    public static function view_hello() {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; &#039;&amp;lt;h1 class=&amp;quot;text-center&amp;quot;&amp;gt;{{ &amp;quot;plugin.local_hello.hello&amp;quot; | translate }}&amp;lt;/h1&amp;gt;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;lang/en/local_hello.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;hello&#039;] = &#039;Hello World&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once you&#039;ve done that, try logging into your site in the app and you should see a new button in the main menu or more menu (depending on the device) saying &amp;quot;Hello World&amp;quot;. If you press this button, you should see a page saying &amp;quot;Hello World!&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Congratualtions, you have written your first Moodle plugin with moodle support!&lt;br /&gt;
&lt;br /&gt;
You can read the rest of this page to learn more about mobile plugins and start working on your plugin. Here&#039;s some things to keep in mind:&lt;br /&gt;
* If you change the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file, you will have to refresh the browser. And remember to [https://developer.chrome.com/docs/devtools/network/reference/#disable-cache disable the network cache].&lt;br /&gt;
* If you change an existing template or function, you won’t have to refresh the browser. In most cases, doing a PTR (Pull To Refresh) in the page that displays the template will suffice.&lt;br /&gt;
* If any of these doesn&#039;t show your changes, you may need to [https://docs.moodle.org/311/en/Developer_tools#Purge_all_caches purge all caches] to avoid problems with the auto-loading cache.&lt;br /&gt;
* Ultimately, if that doesn&#039;t work either, you may have to log out from the site and log in again. If any changes affect plugin installation, you may also need to increase the version in your plugin&#039;s &amp;lt;code&amp;gt;version.php&amp;lt;/code&amp;gt; file and upgrade it in the site.&lt;br /&gt;
==Types of plugins==&lt;br /&gt;
There are 3 types of plugins:&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
[[File:Templates_downloaded_when_requested.png|thumb]]&lt;br /&gt;
With this type of plugin, the template of your plugin will be generated and downloaded when the user opens the plugin in the app. This means that your function will receive some context parameters. For example, if you&#039;re developing a course module plugin you will receive the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; (course module ID). You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
[[File:Templates_downloaded_on_login.png|thumb]]&lt;br /&gt;
With this type of plugin, the template for your plugin will be downloaded when the user logs in into the app and will be stored in the device. This means that your function will not receive any context parameters, and you need to return a generic template that will be built with JS data like the ones in the Moodle App. When the user opens a page that includes your plugin, your template will receive the required JS data and your template will be rendered. You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
You can always implement the whole plugin yourself using JavaScript instead of using our API. In fact, this is required if you want to implement some features like capturing links in the Mobile app. You can see the list of delegates that only support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
==Step by step example==&lt;br /&gt;
In this example, we are going to update an existing plugin, the [https://github.com/mdjnelson/moodle-mod_certificate Certificate activity module], that previously used a [[Moodle Mobile 2 (Ionic 1) Remote add-ons|Remote add-on]] (a legacy approach to implement mobile plugins).&lt;br /&gt;
&lt;br /&gt;
This is a simple activity module that displays the certificate issued for the current user along with the list of the dates of previously issued certificates. It also stores in the course log that the user viewed a certificate. This module also works offline: when the user downloads the course or activity, the data is pre-fetched and can be viewed offline.&lt;br /&gt;
===Step 1. Update the &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file===&lt;br /&gt;
In this case, we are updating an existing file. For new plugins, you should create this new file.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;mod_certificate&#039; =&amp;gt; [ // Plugin identifier&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;coursecertificate&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/mod/certificate/pix/icon.gif&#039;,&lt;br /&gt;
                    &#039;class&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
       &lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseModuleDelegate&#039;, // Delegate (where to display the link to the plugin)&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;mobile_course_view&#039;, // Main function in \mod_certificate\output\mobile&lt;br /&gt;
                &#039;offlinefunctions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;mobile_course_view&#039; =&amp;gt; [],&lt;br /&gt;
                    &#039;mobile_issues_view&#039; =&amp;gt; [],&lt;br /&gt;
                ], // Function that needs to be downloaded for offline.&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [ // Language strings that are used in all the handlers.&lt;br /&gt;
            [&#039;pluginname&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;summaryofattempts&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;getcertificate&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;requiredtimenotmet&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;viewcertificateviews&#039;, &#039;certificate&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Plugin identifier:&lt;br /&gt;
: A unique name for the plugin, it can be anything (there’s no need to match the module name).&lt;br /&gt;
&lt;br /&gt;
;Handlers (Different places where the plugin will display content):&lt;br /&gt;
: A plugin can be displayed in different views in the app. Each view should have a unique name inside the plugin scope (alphanumeric).&lt;br /&gt;
&lt;br /&gt;
; Display data:&lt;br /&gt;
: This is only needed for certain types of plugins. Also, depending on the type of delegate it may require additional (or less fields). In this case, we are indicating the module icon.&lt;br /&gt;
&lt;br /&gt;
; Delegate&lt;br /&gt;
: Where to display the link to the plugin, see the [[#Delegates|Delegates]] section for all the possible options.&lt;br /&gt;
&lt;br /&gt;
; Method:&lt;br /&gt;
: This is the method in the Moodle &amp;lt;code&amp;gt;\{component-name}\output\mobile&amp;lt;/code&amp;gt; class to be executed the first time the user clicks in the new option displayed in the app.&lt;br /&gt;
&lt;br /&gt;
; Offlinefunctions&lt;br /&gt;
: This is the list of functions that need to be called and stored when the user downloads a course for offline usage. Please note that you can add functions here that are not even listed in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. &lt;br /&gt;
: In our example, downloading for offline access will mean that we&#039;ll execute the functions for getting the certificate and issued certificates passing as parameters the current &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; (and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; when we are using the mod or course delegate). If we have the result of those functions stored in the app, we&#039;ll be able to display the certificate information even if the user is offline.&lt;br /&gt;
: Offline functions will be mostly used to display information for final users, any further interaction with the view won’t be supported offline (for example, trying to send information when the user is offline).&lt;br /&gt;
: You can indicate here other Web Services functions, indicating the parameters that they might need from a defined subset (currently &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Prefetching the module will also download all the files returned by the methods in these offline functions (in the &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; array).&lt;br /&gt;
: Note that if your functions use additional custom parameters (for example, if you implement multiple pages within a module&#039;s view function by using a &amp;lt;code&amp;gt;page&amp;lt;/code&amp;gt; parameter in addition to the usual &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;) then the app will not know which additional parameters to supply. In this case, do not list the function in &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt;; instead, you will need to manually implement a [[#Module_prefetch_handler|module prefetch handler]].&lt;br /&gt;
&lt;br /&gt;
;Lang:&lt;br /&gt;
: The language pack string ids used in the plugin by all the handlers. Normally these will be strings from your own plugin, however, you can list any strings you need here, like &amp;lt;code&amp;gt;[&#039;cancel&#039;, &#039;moodle&#039;]&amp;lt;/code&amp;gt;. If you do this, be warned that in the app you will then need to refer to that string as &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.myplugin.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (not &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.moodle.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Please only include the strings you actually need. The Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform, and this will then be cached, so listing too many strings is very wasteful.&lt;br /&gt;
There are additional attributes supported by the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; list, you can find about them in the [[#Mobile.php_supported_options|Mobile.php supported options]] section.&lt;br /&gt;
===Step 2. Creating the main function===&lt;br /&gt;
The main function displays the current issued certificate (or several warnings if it’s not possible to issue a certificate). It also displays a link to view the dates of previously issued certificates.&lt;br /&gt;
&lt;br /&gt;
All the functions must be created in the plugin or subsystem &amp;lt;code&amp;gt;classes/output&amp;lt;/code&amp;gt; directory, the name of the class must be &amp;lt;code&amp;gt;mobile&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
For this example, the namespace name will be &amp;lt;code&amp;gt;mod_certificate\output&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace mod_certificate\output;&lt;br /&gt;
&lt;br /&gt;
use context_module;&lt;br /&gt;
use mod_certificate_external;&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Returns the certificate course view for the mobile app.&lt;br /&gt;
     *&lt;br /&gt;
     * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
     *&lt;br /&gt;
     * @return array       HTML, JS and other data.&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
        $args = (object) $args;&lt;br /&gt;
        $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
        // Capabilities check.&lt;br /&gt;
        require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
        $context = \context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
        require_capability(&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
        if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
            require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
        }&lt;br /&gt;
        $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
        // Get certificates from external (taking care of exceptions).&lt;br /&gt;
        try {&lt;br /&gt;
            $issued = \mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
            $certificates = \mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
            $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $issues = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Set timemodified for each certificate.&lt;br /&gt;
        foreach ($issues as $issue) {&lt;br /&gt;
            if (empty($issue-&amp;gt;timemodified)) {&lt;br /&gt;
                $issue-&amp;gt;timemodified = $issue-&amp;gt;timecreated;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $showget = true;&lt;br /&gt;
        if ($certificate-&amp;gt;requiredtime &amp;amp;&amp;amp; !has_capability(&#039;mod/certificate:manage&#039;, $context)) {&lt;br /&gt;
            if (certificate_get_course_time($certificate-&amp;gt;course) &amp;lt; ($certificate-&amp;gt;requiredtime * 60)) {&lt;br /&gt;
                $showget = false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $certificate-&amp;gt;name = format_string($certificate-&amp;gt;name);&lt;br /&gt;
        [$certificate-&amp;gt;intro, $certificate-&amp;gt;introformat] =&lt;br /&gt;
                external_format_text($certificate-&amp;gt;intro, $certificate-&amp;gt;introformat, $context-&amp;gt;id, &#039;mod_certificate&#039;, &#039;intro&#039;);&lt;br /&gt;
        $data = [&lt;br /&gt;
            &#039;certificate&#039; =&amp;gt; $certificate,&lt;br /&gt;
            &#039;showget&#039; =&amp;gt; $showget &amp;amp;&amp;amp; count($issues) &amp;gt; 0,&lt;br /&gt;
            &#039;issues&#039; =&amp;gt; $issues,&lt;br /&gt;
            &#039;issue&#039; =&amp;gt; $issues[0],&lt;br /&gt;
            &#039;numissues&#039; =&amp;gt; count($issues),&lt;br /&gt;
            &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
            &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;files&#039; =&amp;gt; $issues,&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Function declaration: &lt;br /&gt;
: The function name is the same as the one used in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file (&amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; field). There is only one argument, &amp;lt;code&amp;gt;$args&amp;lt;/code&amp;gt;, which is an array containing all the information sent by the mobile app (the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversionname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversioncode&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;applang&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appcustomurlscheme&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
&lt;br /&gt;
; Function implementation:&lt;br /&gt;
: In the first part of the function, we check permissions and capabilities (like a &amp;lt;code&amp;gt;view.php&amp;lt;/code&amp;gt; script would do normally). Then we retrieve the certificate information that’s necessary to display the template.&lt;br /&gt;
&lt;br /&gt;
; Function return:&lt;br /&gt;
* &amp;lt;code&amp;gt;templates&amp;lt;/code&amp;gt; — The rendered template (notice that we could return more than one template, but we usually would only need one). By default the app will always render the first template received, the rest of the templates can be used if the plugin defines some JavaScript code.&lt;br /&gt;
* &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; — Empty, because we don’t need any in this case.&lt;br /&gt;
* &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; — Empty as well, because we don’t need any additional data to be used by directives or components in the template. This field will be published as an object supporting 2-way data-binding in the template.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; — A list of files that the app should be able to download (for offline usage mostly).&lt;br /&gt;
===Step 3. Creating the template for the main function===&lt;br /&gt;
This is the most important part of your plugin because it contains the code that will be rendered on the mobile app.&lt;br /&gt;
&lt;br /&gt;
In this template we’ll be using Ionic, together with directives and components specific to the Moodle App.&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;ion-&amp;lt;/code&amp;gt; are ionic components. Most of the time, the component name is self-explanatory but you may refer to a detailed guide here: https://ionicframework.com/docs/components/&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;core-&amp;lt;/code&amp;gt; are custom components of the Moodle App.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_page.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;core-course-module-description description=&amp;quot;&amp;lt;% certificate.intro %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-course-module-description&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;ion-list-header&amp;gt;&lt;br /&gt;
            &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.summaryofattempts&#039; | translate }}&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-list-header&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-new-content title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; &lt;br /&gt;
                        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
                        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
                    {{ &#039;plugin.mod_certificate.viewcertificateviews&#039; | translate: {$a: &amp;lt;% numissues %&amp;gt;} }}&lt;br /&gt;
                &amp;lt;/ion-button&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&lt;br /&gt;
                        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
                        [files]=&amp;quot;[{&lt;br /&gt;
                            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
                            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
                            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
                        }]&amp;quot;&amp;gt;&lt;br /&gt;
                 &lt;br /&gt;
                        &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot; slot=&amp;quot;start&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                            {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
                    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%^showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;p&amp;gt;{{ &#039;plugin.mod_certificate.requiredtimenotmet&#039; | translate }}&amp;lt;/p&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;!-- Call log WS when the template is loaded. --&amp;gt;&lt;br /&gt;
        &amp;lt;span core-site-plugins-call-ws-on-load name=&amp;quot;mod_certificate_view_certificate&amp;quot;&lt;br /&gt;
                [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/span&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache).&lt;br /&gt;
&lt;br /&gt;
Then we display the module description using &amp;lt;code&amp;gt;core-course-module-description&amp;lt;/code&amp;gt;, which is a component used to include the course module description.&lt;br /&gt;
&lt;br /&gt;
For displaying the certificate information we create a list of elements, adding a header on top.&lt;br /&gt;
&lt;br /&gt;
The following line using the &amp;lt;code&amp;gt;translate&amp;lt;/code&amp;gt; filter indicates that the app will translate the &amp;lt;code&amp;gt;summaryofattempts&amp;lt;/code&amp;gt; string id (here we could’ve used mustache translation but it is usually better to delegate the strings translations to the app). The string id has the following format: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
plugin.{plugin-identifier}.{string-id}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Where &amp;lt;code&amp;gt;{plugin-identifier}&amp;lt;/code&amp;gt; is taken from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;{string-id}&amp;lt;/code&amp;gt; must be indicated in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; field in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Then, we display a button to transition to another page if there are certificates issued. The attribute (directive) &amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt; indicates that if the user clicks the button, we need to call the &amp;lt;code&amp;gt;mobile_issues_view&amp;lt;/code&amp;gt; function in the &amp;lt;code&amp;gt;mod_certificate&amp;lt;/code&amp;gt; component; passing as arguments the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;. The content returned by this function will be displayed in a new page (read the following section to see the code of this new page).&lt;br /&gt;
&lt;br /&gt;
Just after this button, we display another one but this time for downloading an issued certificate. The &amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt; directive indicates that clicking this button is for downloading the whole activity and opening the main file. This means that, when the user clicks this button, the whole certificate activity will be available offline.&lt;br /&gt;
&lt;br /&gt;
Finally, just before the &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt; is closed, we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt; directive to indicate that once the page is loaded, we need to call a Web Service function in the server, in this case we are calling the &amp;lt;code&amp;gt;mod_certificate_view_certificate&amp;lt;/code&amp;gt; that will log that the user viewed this page.&lt;br /&gt;
&lt;br /&gt;
As you can see, no JavaScript was necessary at all. We used plain HTML elements and attributes that did all the complex dynamic logic (like calling a Web Service) behind the scenes.&lt;br /&gt;
===Step 4. Adding an additional page===&lt;br /&gt;
Add the following method to &amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Returns the certificate issues view for the mobile app.&lt;br /&gt;
 * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
 *&lt;br /&gt;
 * @return array       HTML, JS and other data.&lt;br /&gt;
 */&lt;br /&gt;
public static function mobile_issues_view($args) {&lt;br /&gt;
    global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
    $args = (object) $args;&lt;br /&gt;
    $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
    // Capabilities check.&lt;br /&gt;
    require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
    $context = context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
    require_capability (&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
    if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
        require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
    }&lt;br /&gt;
    $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
    // Get certificates from external (taking care of exceptions).&lt;br /&gt;
    try {&lt;br /&gt;
        $issued = mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
        $certificates = mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
        $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
    } catch (Exception $e) {&lt;br /&gt;
        $issues = [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $data = [&#039;issues&#039; =&amp;gt; $issues];&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
            [&lt;br /&gt;
                &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_issues&#039;, $data),&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
        &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This method for the new page was added just after &amp;lt;code&amp;gt;mobile_course_view&amp;lt;/code&amp;gt;, the code is quite similar: checks the capabilities, retrieves the information required for the template, and returns the template rendered.&lt;br /&gt;
&lt;br /&gt;
The code of the mustache template is also very simple.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_issues.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &amp;lt;%timecreated%&amp;gt; | coreToLocaleString }}&amp;lt;/p&amp;gt;&lt;br /&gt;
                    &amp;lt;p&amp;gt;&amp;lt;%grade%&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As we did in the previous template, in the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache). &lt;br /&gt;
&lt;br /&gt;
Here we are creating an Ionic list that will display a new item in the list per each issued certificated.&lt;br /&gt;
&lt;br /&gt;
For the issued certificated we’ll display the time when it was created (using the app filter &amp;lt;code&amp;gt;coreToLocaleString&amp;lt;/code&amp;gt;). We are also displaying the grade displayed in the certificate (if any).&lt;br /&gt;
===Step 5. Plugin webservices, if included===&lt;br /&gt;
If your plugin uses its own web services, they will also need to be enabled for mobile access in your &amp;lt;code&amp;gt;db/services.php&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
The following line should be included in each webservice definition:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;services&#039; =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/db/services.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$functions = [&lt;br /&gt;
    &#039;mod_certificate_get_certificates_by_courses&#039; =&amp;gt; [&lt;br /&gt;
        &#039;classname&#039;     =&amp;gt; &#039;mod_certificate_external&#039;,&lt;br /&gt;
        &#039;methodname&#039;    =&amp;gt; &#039;get_certificates_by_courses&#039;,&lt;br /&gt;
        &#039;description&#039;   =&amp;gt; &#039;Returns a list of certificate instances...&#039;,&lt;br /&gt;
        &#039;type&#039;          =&amp;gt; &#039;read&#039;,&lt;br /&gt;
        &#039;capabilities&#039;  =&amp;gt; &#039;mod/certificate:view&#039;,&lt;br /&gt;
        &#039;services&#039;      =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Mobile.php supported options==&lt;br /&gt;
In the previous section, we learned about some of the existing options for handlers configuration. This is the full list of supported options.&lt;br /&gt;
===Common options===&lt;br /&gt;
* &amp;lt;code&amp;gt;delegate&amp;lt;/code&amp;gt; (mandatory) — Name of the delegate to register the handler in.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (mandatory) — The method to call to retrieve the main page content.&lt;br /&gt;
* &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; (optional) — A method to call to retrieve the initialisation JS and the restrictions to apply to the whole handler. It can also return templates that can be used from JavaScript. You can learn more about this in the [[#Initialisation|Initialisation]] section.&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttocurrentuser&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForUser&amp;lt;/code&amp;gt; function. If true, the handler will only be shown for the current user. For more info about displaying the plugin only for certain users, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttoenrolledcourses&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForCourse&amp;lt;/code&amp;gt; function. If true or not defined, the handler will only be shown for courses the user is enrolled in. For more info about displaying the plugin only for certain courses, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;styles&amp;lt;/code&amp;gt; (optional) — An array with two properties: &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt;. The URL should point to a CSS file, either using an absolute URL or a relative URL. This file will be downloaded and applied by the app. It&#039;s recommended to include styles that will only affect your plugin templates. The version number is used to determine if the file needs to be downloaded again, you should change the version number everytime you change the CSS file.&lt;br /&gt;
* &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; (optional) — If your plugin supports a component in the app different than the one defined by your plugin, you can use this property to specify it. For example, you can create a local plugin to support a certain course format, activity, etc. The component of your plugin in Moodle would be &amp;lt;code&amp;gt;local_whatever&amp;lt;/code&amp;gt;, but in &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; you can specify that this handler will implement &amp;lt;code&amp;gt;format_whatever&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;mod_whatever&amp;lt;/code&amp;gt;. This property was introduced in the version 3.6.1 of the app.&lt;br /&gt;
===Options only for CoreMainMenuDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. Main Menu plugins are always displayed in the More tab, they cannot be displayed as tabs in the bottom bar.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreMainMenuHomeDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseOptionsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ismenuhandler&amp;lt;/code&amp;gt; (optional) — Supported from the 3.7.1 version of the app. Set it to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if you want your plugin to be displayed in the contextual menu of the course instead of in the top tabs. The contextual menu is displayed when you click in the 3-dots button at the top right of the course.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseModuleDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — Path to the module icon. After Moodle app 4.0, this icon is only used as a fallback, the app will always try to use the theme icon so themes can override icons in the app.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (optional) — The function to call to retrieve the main page content. In this delegate the method is optional. If the method is not set, the module won&#039;t be clickable.&lt;br /&gt;
* &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt; (optional) — List of functions to call when prefetching the module. It can be a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; method or a WS. You can filter the params received by the WS. By default, WS will receive these params: &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;. Other valid values that will be added if they are present in the list of params: &amp;lt;code&amp;gt;courseids&amp;lt;/code&amp;gt; (it will receive a list with the courses the user is enrolled in), &amp;lt;code&amp;gt;{component}id&amp;lt;/code&amp;gt; (For example, &amp;lt;code&amp;gt;certificateid&amp;lt;/code&amp;gt;).&lt;br /&gt;
* &amp;lt;code&amp;gt;downloadbutton&amp;lt;/code&amp;gt; (optional) — Whether to display download button in the module. If not defined, the button will be shown if there is any offlinefunction.&lt;br /&gt;
* &amp;lt;code&amp;gt;isresource&amp;lt;/code&amp;gt; (optional) — Whether the module is a resource or an activity. Only used if there is any offline function. If your module relies on the &amp;lt;code&amp;gt;contents&amp;lt;/code&amp;gt; field, then it should be &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;updatesnames&amp;lt;/code&amp;gt; (optional) — Only used if there is any offline function. A regular expression to check if there&#039;s any update in the module. It will be compared to the result of &amp;lt;code&amp;gt;core_course_check_updates&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayopeninbrowser&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Open in browser&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayOpenInBrowser = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydescription&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Description&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayDescription = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayrefresh&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Refresh&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayRefresh = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayprefetch&amp;lt;/code&amp;gt; (optional) — Whether the module should display the download option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayPrefetch = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysize&amp;lt;/code&amp;gt; (optional) — Whether the module should display the downloaded size in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displaySize = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; (optional) — It can be used to specify the supported features of the plugin. Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. It should be an array with features as keys (For example, &amp;lt;code&amp;gt;[FEATURE_NO_VIEW_LINK =&amp;gt; true&amp;lt;/code&amp;gt;). If you need to calculate this dynamically please see [[#Module_plugins:_dynamically_determine_if_a_feature_is_supported|Module plugins: dynamically determine if a feature is supported]]. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; (optional) — If set, this method will be called when the course is rendered and the HTML returned will be displayed in the course page for the module. Please notice the HTML returned should not contain directives or components, only default HTML. Supported from the 3.8 version of the app.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseFormatDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;canviewallsections&amp;lt;/code&amp;gt; (optional) — Whether the course format allows seeing all sections in a single page. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayenabledownload&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, it&#039;s no longer used.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysectionselector&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, use &#039;&#039;displaycourseindex&#039;&#039; instead.&lt;br /&gt;
*&amp;lt;code&amp;gt;displaycourseindex&amp;lt;/code&amp;gt; (optional) — Whether the default course index should be displayed. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
===Options only for CoreUserDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — The type of the addon. The values accepted are &amp;lt;code&amp;gt;&#039;newpage&#039;&amp;lt;/code&amp;gt; (default) and &amp;lt;code&amp;gt;&#039;communication&#039;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreSettingsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for AddonMessageOutputDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
**&amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreBlockDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (optional):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section. If this is not supplied, it will default to &amp;lt;code&amp;gt;&#039;plugins.block_{block-name}.pluginname&#039;&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class. If this is not supplied, it will default to &amp;lt;code&amp;gt;block_{block-name}&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — Possible values are:&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;title&amp;quot;&amp;lt;/code&amp;gt; — Your block will only display the block title, and when it&#039;s clicked it will open a new page to display the block contents (the template returned by the block&#039;s method).&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;prerendered&amp;quot;&amp;lt;/code&amp;gt; — Your block will display the content and footer returned by the WebService to get the blocks (for example, &amp;lt;code&amp;gt;core_block_get_course_blocks&amp;lt;/code&amp;gt;), so your block&#039;s method will never be called.&lt;br /&gt;
*** Any other value — Your block will immediately call the method specified in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and it will use the template to render the block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fallback&amp;lt;/code&amp;gt; (optional) — This option allows you to specify a block to use in the app instead of your block. For example, you can make the app display the &amp;quot;My overview&amp;quot; block instead of your block in the app by setting &amp;lt;code&amp;gt;&#039;fallback&#039; =&amp;gt; &#039;myoverview&#039;&amp;lt;/code&amp;gt;. The fallback will only be used if you don&#039;t specify a &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; is different to &amp;lt;code&amp;gt;&#039;title&#039;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&#039;prerendered&#039;&amp;lt;/code&amp;gt;. Supported from the 3.9.0 version of the app.&lt;br /&gt;
==Delegates==&lt;br /&gt;
Delegates can be classified by type of plugin. For more info about type of plugins, please see the [[#Types_of_plugins|Types of plugins]] section.&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new items to the main menu (currently displayed at the bottom of the app). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuHomeDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new tabs in the home page (by default the app is displaying the &amp;quot;Dashboard&amp;quot; and &amp;quot;Site home&amp;quot; tabs). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new options in a course (Participants or Grades are examples of this type of delegate).&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting activity modules or resources.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add additional options in the user profile page in the app.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting course formats. When you open a course from the course list in the mobile app, it will check if there is a &amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt; handler for the format that site uses. If so, it will display the course using that handler. Otherwise, it will use the default app course format.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile course formats]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreSettingsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to add a new option in the settings page.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonMessageOutputDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a message output plugin.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreBlockDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a block. For example, blocks can be displayed in Site Home, Dashboard and the Course page.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question types.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile question types]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionBehaviourDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question behaviours.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting user profile fields.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModQuizAccessRuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a quiz access rule.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModAssignSubmissionDelegate&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;AddonModAssignFeedbackDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use these delegates to support assign submission or feedback plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonWorkshopAssessmentStrategyDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a workshop assessment strategy plugin.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
These delegates require JavaScript to be supported. See [[#Initialisation|Initialisation]] for more information.&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreContentLinksDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreCourseModulePrefetchDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFileUploaderDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CorePluginFileDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFilterDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
==Available components and directives==&lt;br /&gt;
===Difference between components and directives===&lt;br /&gt;
A directive is usually represented as an HTML attribute, allows you to extend a piece of HTML with additional information or functionality. Example of directives are: &amp;lt;code&amp;gt;core-auto-focus&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*ngIf&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ng-repeat&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components are also directives, but they are usually represented as an HTML tag and they are used to add custom elements to the app. Example of components are &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-item&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;core-search-box&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components and directives are Angular concepts; you can learn more about them and the components come out of the box with Ionic in the following links:&lt;br /&gt;
* [https://angular.io/guide/built-in-directives Angular directives documentation]&lt;br /&gt;
* [https://ionicframework.com/docs/components Ionic components]&lt;br /&gt;
===Custom core components and directives===&lt;br /&gt;
These are some useful custom components and directives that are only available in the Moodle App. Please note that this isn’t the full list of custom components and directives, it’s just an extract of the most common ones.&lt;br /&gt;
&lt;br /&gt;
You can find a full list of components and directives in the source code of the app, within [https://github.com/moodlehq/moodleapp/tree/master/src/core/components &amp;lt;code&amp;gt;src/core/components&amp;lt;/code&amp;gt;] and [https://github.com/moodlehq/moodleapp/tree/master/src/core/directives &amp;lt;code&amp;gt;src/core/directives&amp;lt;/code&amp;gt;].&lt;br /&gt;
====&amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;====&lt;br /&gt;
This directive formats the text and adds some directives needed for the app to work as it should. For example, it treats all links and all the embedded media so they work fine in the app. If some content in your template includes links or embedded media, please use this directive.&lt;br /&gt;
&lt;br /&gt;
This directive automatically applies &amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt; to all the links and embedded media.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;text&amp;lt;/code&amp;gt; (string) — The text to format.&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;adaptImg&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to adapt images to screen width. &lt;br /&gt;
* &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether all HTML tags should be removed.&lt;br /&gt;
* &amp;lt;code&amp;gt;singleLine&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether new lines should be removed to display all the text in single line. Only if &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; (number) — Optional. Max height in pixels to render the content box. The minimum accepted value is 50. Using this parameter will force &amp;lt;code&amp;gt;display: block&amp;lt;/code&amp;gt; to calculate the height better. If you want to avoid this, use &amp;lt;code&amp;gt;class=&amp;quot;inline&amp;quot;&amp;lt;/code&amp;gt; at the same time to use &amp;lt;code&amp;gt;display: inline-block&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-format-text text=&amp;quot;&amp;lt;% cm.description %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====&amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle a link. It performs several checks, like checking if the link needs to be opened in the app, and opens the link as it should (without overriding the app).&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;capture&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether the link needs to be captured by the app (check if the link can be handled by the app instead of opening it in a browser).&lt;br /&gt;
* &amp;lt;code&amp;gt;inApp&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to open in an embedded browser within the app or in the system browser.&lt;br /&gt;
* &amp;lt;code&amp;gt;autoLogin&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt;. If the link should be open with auto-login. Accepts the following values:&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;yes&amp;quot;&amp;lt;/code&amp;gt; — Always auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;no&amp;quot;&amp;lt;/code&amp;gt; — Never auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt; — Auto-login only if it points to the current site.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a href=&amp;quot;&amp;lt;% cm.url %&amp;gt;&amp;quot; core-link&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle links to files and embedded files. This directive should be used in any link to a file or any embedded file that you want to have available when the app is offline. &lt;br /&gt;
&lt;br /&gt;
If a file is downloaded, its URL will be replaced by the local file URL.&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;&amp;lt;% event.iconurl %&amp;gt;&amp;quot; core-external-content component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% event.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-user-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to go to user profile on click. When the user clicks the element where this directive is attached, the right user profile will be opened.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;userId&amp;lt;/code&amp;gt; (number) — User id to open the profile.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — Optional. Course id to show the user info related to that course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a ion-item core-user-link userId=&amp;quot;&amp;lt;% userid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to handle a remote file. It shows the file name, icon (depending on mime type) and a button to download or refresh it. The user can identify if the file is downloaded or not based on the button.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;file&amp;lt;/code&amp;gt; (object) — The file. Must have a &amp;lt;code&amp;gt;filename&amp;lt;/code&amp;gt; property and either &amp;lt;code&amp;gt;fileurl&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component the file belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDelete&amp;lt;/code&amp;gt; (boolean) — Optional. Whether the file can be deleted.&lt;br /&gt;
* &amp;lt;code&amp;gt;alwaysDownload&amp;lt;/code&amp;gt; (boolean) — Optional. Whether it should always display the refresh button when the file is downloaded. Use it for files that you cannot determine if they&#039;re outdated or not.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDownload&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether file can be downloaded. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-file&lt;br /&gt;
        [file]=&amp;quot;{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.url %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.name %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            filesize: &#039;&amp;lt;% issue.size %&amp;gt;&#039;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/core-file&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening a file. When the item with this directive is clicked, the file will be downloaded (if needed) and opened.&lt;br /&gt;
&lt;br /&gt;
It is usually recommended to use the &amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt; component since it also displays the state of the file.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt; (object) — The file to download.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. Component ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage (a button to download a file):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button&lt;br /&gt;
        [core-download-file]=&amp;quot;{&lt;br /&gt;
            fileurl: &amp;lt;% issue.url %&amp;gt;,&lt;br /&gt;
            timemodified: &amp;lt;% issue.timemodified %&amp;gt;,&lt;br /&gt;
            filesize: &amp;lt;% issue.size %&amp;gt;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.download | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening the main file of a module.&lt;br /&gt;
&lt;br /&gt;
When the item with this directive is clicked, the whole module will be downloaded (if needed) and its main file opened. This is meant for modules like &amp;lt;code&amp;gt;mod_resource&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This directive must receive either a &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; or a &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. If no files are provided, it will use &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; (object) — Optional, required if module is not supplied. The module object.&lt;br /&gt;
* &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt; (number) — Optional, required if module is not supplied. The module ID.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — The course ID the module belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional, defaults to the same value as &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. Component ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; (object[]) — Optional. List of files of the module. If not provided, uses &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot; &lt;br /&gt;
        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        [files]=&amp;quot;[{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
        }]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-navbar-buttons&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to add buttons to the app&#039;s header without having to place them inside the header itself. Using this component in a site plugin will allow adding buttons to the header of the current page.&lt;br /&gt;
&lt;br /&gt;
If this component indicates a position (start/end), the buttons will only be added if the header has some buttons in that position. If no start/end is specified, then the buttons will be added to the first &amp;lt;code&amp;gt;&amp;lt;ion-buttons&amp;gt;&amp;lt;/code&amp;gt; found in the header.&lt;br /&gt;
&lt;br /&gt;
You can use the &amp;lt;code&amp;gt;[hidden]&amp;lt;/code&amp;gt; input to hide all the inner buttons if a certain condition is met.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons end&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button (click)=&amp;quot;action()&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-icon slot=&amp;quot;icon-only&amp;quot; name=&amp;quot;funnel&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can also use this to add options to the context menu, for example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons&amp;gt;&lt;br /&gt;
    &amp;lt;core-context-menu&amp;gt;&lt;br /&gt;
        &amp;lt;core-context-menu-item&lt;br /&gt;
                [priority]=&amp;quot;500&amp;quot; content=&amp;quot;Nice boat&amp;quot; (action)=&amp;quot;boatFunction()&amp;quot;&lt;br /&gt;
                iconAction=&amp;quot;boat&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/core-context-menu-item&amp;gt;&lt;br /&gt;
    &amp;lt;/core-context-menu&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Using &#039;font&#039; icons with &amp;lt;code&amp;gt;ion-icon&amp;lt;/code&amp;gt;====&lt;br /&gt;
Font icons are widely used on the app and Moodle LMS website. In order to support [https://fontawesome.com/v5/search?m=free font awesome icons]. We&#039;ve added a directive that uses prefixes on the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; attribute to use different font icons.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fas-&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;fa-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=solid Font awesome solid] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;far-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=regular Font awesome regular] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fab-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=brands Font awesome brands] library (But only a few are supported and we discourage to use them).&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;moodle-&amp;lt;/code&amp;gt; will use some svg icons [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/moodle imported from Moodle LMS].&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fam-&amp;lt;/code&amp;gt; will use [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/font-awesome customized font awesome icons].&lt;br /&gt;
* If the prefix is not found or not valid, the app will search the icon name on the [https://ionic.io/ionicons ionicons library].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example of usage to show icon &amp;quot;pizza-slice&amp;quot; from font-awesome regular library:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-icon name=&amp;quot;fas-pizza-slice&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We encourage the use of font-awesome icons to match the appearance from the LMS website version.&lt;br /&gt;
===Specific component and directives for plugins===&lt;br /&gt;
These are component and directives created specifically for supporting Moodle plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to display a new content when clicked. This new content can be displayed in a new page or in the current page (only if the current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The params to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in v3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in same page or open a new one. &lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that doing &amp;lt;code&amp;gt;[useOtherData]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to go to a new content page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content &lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; &lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to load new content in current page using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot; samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked. The action to do when the WS call is successful depends on the provided data: display a message, go back or refresh current view.&lt;br /&gt;
&lt;br /&gt;
If you want to load a new content when the WS call is done, please see [[#core-site-plugins-call-ws-new-content|&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The params for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;successMessage&amp;lt;/code&amp;gt; (string) — Message to show on success. If not supplied, no message. If supplied but empty, defaults to &amp;quot;Success&amp;quot;.&lt;br /&gt;
* &amp;lt;code&amp;gt;goBackOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to go back if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to refresh the current view if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to send some data to the server without using cache, displaying default messages and refreshing on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage successMessage&lt;br /&gt;
        refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to send some data to the server using cache without confirming, going back on success and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        goBackOnSuccess=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Same as the previous example, but implementing custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;certificateViewed($event)&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.certificateViewed = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked and load a new content passing the WS result as arguments. This new content can be displayed in a new page or in the same page (only if current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to load some new content when done, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The parameters to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in the same page or open a new one.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. The format is the same as in &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;jsData&amp;lt;/code&amp;gt; (any) — JS variables to pass to the new page so they can be used in the template or JS. If &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; is supplied instead of an object, all initial variables from current page will be copied. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;newContentPreSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in 3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to get some data from the server without using cache, showing default confirm and displaying a new page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage&lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to get some data from the server using cache, without confirm, displaying new content in same page and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Same as the previous example, but implementing a custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS as soon as the template is loaded. This directive is meant for actions to do in the background, like calling logging Web Services.&lt;br /&gt;
&lt;br /&gt;
If you want to call a WS when the user clicks on a certain element, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span core-site-plugins-call-ws-on-load&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Advanced features==&lt;br /&gt;
===Display the plugin only if certain conditions are met===&lt;br /&gt;
You might want to display your plugin in the mobile app only if certain dynamic conditions are met, so the plugin would be displayed only for some users. This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section ahead).&lt;br /&gt;
&lt;br /&gt;
All initialisation methods are called as soon as your plugin is retrieved. If you don&#039;t want your plugin to be displayed for the current user, then you should return the following in the initialisation method (only for Moodle site 3.8 and onwards):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;disabled&#039; =&amp;gt; true];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If the Moodle site is older than 3.8, then the initialisation method should return this instead:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;javascript&#039; =&amp;gt; &#039;this.HANDLER_DISABLED&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
On the other hand, you might want to display a plugin only for certain courses (&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;) or only if the user is viewing certain users&#039; profiles (&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;). This can be achieved with the initialisation method too.&lt;br /&gt;
&lt;br /&gt;
In the initialisation method you can return a &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; property with two fields in it: &amp;lt;code&amp;gt;courses&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;users&amp;lt;/code&amp;gt;. If you return a list of courses IDs in this property, then your plugin will only be displayed when the user views any of those courses. In the same way, if you return a list of user IDs then your plugin will only be displayed when the user views any of those users&#039; profiles.&lt;br /&gt;
===Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;===&lt;br /&gt;
The values returned by the functions in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; are added to a variable so they can be used both in JavaScript and in templates. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call is added to a variable named &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt;, while the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call is added to a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call will be passed to the JS and template of all the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; calls in that handler. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call will only be passed to the JS and template returned by that &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call.&lt;br /&gt;
&lt;br /&gt;
This means that, in your JavaScript, you can access and use the data like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.CONTENT_OTHERDATA.myVar;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the template you could use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
{{ CONTENT_OTHERDATA.myVar }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; is the name we put to one of our variables, it can be any name that you want. In the example above, this is the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
[&#039;myVar&#039; =&amp;gt; &#039;Initial value&#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Example====&lt;br /&gt;
In our plugin, we want to display an input text with a certain initial value. When the user clicks a button, we want the value in the input to be sent to a certain Web Service. This can be done using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We will return the initial value of the input in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; of our PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;myVar&#039; =&amp;gt; &#039;My initial value&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then in the template we will use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item text-wrap&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label position=&amp;quot;stacked&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.textlabel | translate }}&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;text&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.myVar&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label&amp;gt;&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws name=&amp;quot;mod_certificate_my_webservice&amp;quot; [useOtherDataForWS]=&amp;quot;[&#039;myVar&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we are creating an input text and we use &amp;lt;code&amp;gt;[(ngModel)]&amp;lt;/code&amp;gt; to use the value in &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; as the initial value and to store the changes in the same &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This means that the initial value of the input will be &amp;quot;My initial value&amp;quot;, and if the user changes the value of the input these changes will be applied to the &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This is called 2-way data binding in Angular.&lt;br /&gt;
&lt;br /&gt;
Then we add a button to send this data to a WS, and for that we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive. We use the &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; attribute to specify which variable from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; we want to send to our WebService. So if the user enters &amp;quot;A new value&amp;quot; in the input and then clicks the button, it will call the WebService &amp;lt;code&amp;gt;mod_certificate_my_webservice&amp;lt;/code&amp;gt; and will send as a parameter &amp;lt;code&amp;gt;[&#039;myVar&#039; =&amp;gt; &#039;A new value&#039;]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We can also achieve the same result using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; attribute of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive instead of using &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws &lt;br /&gt;
        name=&amp;quot;mod_certificate_my_webservice&amp;quot; [params]=&amp;quot;{myVar: CONTENT_OTHERDATA.myVar}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The Web Service call will be exactly the same with both versions.&lt;br /&gt;
&lt;br /&gt;
Notice that this example could be done without using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; too, using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; input of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive.&lt;br /&gt;
===Running JS code after a content template has loaded===&lt;br /&gt;
When you return JavaScript code from a handler function using the &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; array key, this code is executed immediately after the web service call returns, which may be before the returned template has been rendered into the DOM. &lt;br /&gt;
&lt;br /&gt;
If your code needs to run after the DOM has been updated, you can use &amp;lt;code&amp;gt;setTimeout&amp;lt;/code&amp;gt; to call it. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&lt;br /&gt;
    &#039;template&#039; =&amp;gt; [&lt;br /&gt;
        // ...&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;javascript&#039; =&amp;gt; &#039;setTimeout(function() { console.log(&amp;quot;DOM is available now&amp;quot;); });&#039;,&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    &#039;files&#039; =&amp;gt; [],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice that if you wanted to write a lot of code here, you might be better off putting it in a function defined in the response from an initialisation template, so that it does not get loaded again with each page of content.&lt;br /&gt;
===JS functions visible in the templates===&lt;br /&gt;
The app provides some JavaScript functions that can be used from the templates to update, refresh or view content. These are the functions:&lt;br /&gt;
* &amp;lt;code&amp;gt;openContent(title: string, args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Open a new page to display some new content. You need to specify the &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; of the new page and the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshContent(showSpinner = true)&amp;lt;/code&amp;gt; — Refresh the current content. By default, it will display a spinner while refreshing. If you don&#039;t want it to be displayed, you should pass &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; as a parameter.&lt;br /&gt;
* &amp;lt;code&amp;gt;updateContent(args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Refresh the current content using different parameters. You need to specify the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Group selector=====&lt;br /&gt;
Imagine we have an activity that uses groups and we want to let the user select which group they want to see. A possible solution would be to return all the groups in the same template (hidden), and then show the group user selects. However, we can make it more dynamic and return only the group the user is requesting.&lt;br /&gt;
&lt;br /&gt;
To do so, we&#039;ll use a drop down to select the group. When the user selects a group using this drop down, we&#039;ll update the page content to display the new group.&lt;br /&gt;
&lt;br /&gt;
The main difficulty in this is to tell the view which group needs to be selected when the view is loaded. There are 2 ways to do it: using plain HTML or using Angular&#039;s &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;.&lt;br /&gt;
======Using plain HTML======&lt;br /&gt;
We need to add a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; attribute to the option that needs to be selected. To do so, we need to pre-caclulate the selected option in the PHP code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// Detect which group is selected.&lt;br /&gt;
foreach ($groups as $gid=&amp;gt;$group) {&lt;br /&gt;
    $group-&amp;gt;selected = $gid === $groupid;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$data = [&lt;br /&gt;
    &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
    &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
    &#039;groups&#039; =&amp;gt; $groups,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the code above, we&#039;re retrieving the groups the user can see and then we&#039;re adding a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; boolean to each one to determine which one needs to be selected in the drop down. Finally, we pass the list of groups to the template.&lt;br /&gt;
&lt;br /&gt;
In the template, we display the drop down like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: $event})&amp;quot; interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot; &amp;lt;%#selected%&amp;gt;selected&amp;lt;%/selected%&amp;gt; &amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The &amp;lt;code&amp;gt;ionChange&amp;lt;/code&amp;gt; function will be called every time the user selects a different group with the drop down. We&#039;re using the &amp;lt;code&amp;gt;updateContent&amp;lt;/code&amp;gt; function to update the current view using the new group. &amp;lt;code&amp;gt;$event&amp;lt;/code&amp;gt; is an Angular variable that will have the selected value (in our case, the group ID that was just selected). This is enough to make the group selector work.&lt;br /&gt;
======Using &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;======&lt;br /&gt;
&amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; is an Angular directive that allows storing the value of a certain input or select in a JavaScript variable, and also the opposite way: tell the input or select which value to set. The main problem is that we cannot initialise a JavaScript variable from the template, so we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the PHP function we&#039;ll return the group that needs to be selected in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
        &#039;group&#039; =&amp;gt; $groupid,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we don&#039;t need to iterate over the groups array like in the plain HTML example. However, now we&#039;re returning the group id in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array. As it&#039;s explained in the [[#Using_otherdata|Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;]] section, this &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; is visible in the templates inside a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. So in the template we&#039;ll use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.group&amp;quot;&lt;br /&gt;
        (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: CONTENT_OTHERDATA.group})&amp;quot;&lt;br /&gt;
        interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Use the rich text editor===&lt;br /&gt;
The rich text editor included in the app requires a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt; to work. You can use the &amp;lt;code&amp;gt;FormBuilder&amp;lt;/code&amp;gt; library to create this control (or to create a whole &amp;lt;cide&amp;gt;FormGroup if you prefer).&lt;br /&gt;
&lt;br /&gt;
With the following JavaScript you&#039;ll be able to create a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.control = this.FormBuilder.control(this.CONTENT_OTHERDATA.rte);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we&#039;re using a value returned in &amp;lt;code&amp;gt;OTHERDATA&amp;lt;/code&amp;gt; as the initial value of the rich text editor, but you can use whatever you want.&lt;br /&gt;
&lt;br /&gt;
Then you need to pass this control to the rich text editor in your template:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;core-rich-text-editor item-content [control]=&amp;quot;control&amp;quot; placeholder=&amp;quot;Enter your text here&amp;quot; name=&amp;quot;rte_answer&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-rich-text-editor&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, there are several ways to send the value in the rich text editor to a Web Service to save it. This is one of the simplest options:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_webservice&amp;quot; [params]=&amp;quot;{rte: control.value}&amp;quot; ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re passing the value of the rich text editor as a parameter to our Web Service.&lt;br /&gt;
===Initialisation===&lt;br /&gt;
All handlers can specify an &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; method in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. This method is meant to return some JavaScript code that needs to be executed as soon as the plugin is retrieved.&lt;br /&gt;
&lt;br /&gt;
When the app retrieves all the handlers, the first thing it will do is call the &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt; Web Service with the initialisation method. This WS call will only receive the default arguments.&lt;br /&gt;
&lt;br /&gt;
The app will immediately execute the JavaScript code returned by this WS call. This JavaScript can be used to manually register your handlers in the delegates you want, without having to rely on the default handlers built based on the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; data.&lt;br /&gt;
&lt;br /&gt;
The templates returned by this method will be added to a &amp;lt;code&amp;gt;INIT_TEMPLATES&amp;lt;/code&amp;gt; variable that will be passed to all the JavaScript code of that handler. This means that the JavaScript returned by the initialisation method or the main method can access any of the templates HTML like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.INIT_TEMPLATES[&#039;main&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In this case, &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is the ID of the template we want to use.&lt;br /&gt;
&lt;br /&gt;
The same happens with the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the initialisation method, it is added to an &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt; variable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; field returned by this call will be used to determine if your handler is enabled or not. For example, if your handler is for the delegate &amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt; and you return a list of course ids in &amp;lt;code&amp;gt;restrict.courses&amp;lt;/code&amp;gt;, then your handler will only be enabled in the courses you returned. This only applies to the default handlers, if you register your own handler using the JavaScript code then you should check yourself if the handler is enabled.&lt;br /&gt;
&lt;br /&gt;
Finally, if you return an object in this initialisation JavaScript code, all the properties of that object will be passed to all the JavaScript code of that handler so you can use them when the code is run. For example, if your JavaScript code does something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    MyAddonClass: new MyAddonClass()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then, for the rest of JavaScript code of your handler (for example, the main method) you can use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.MyAddonClass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Link handlers=====&lt;br /&gt;
A link handler allows you to decide what to do when a link with a certain URL is clicked. This is useful, for example, to open your plugin page when a link to your plugin is clicked.&lt;br /&gt;
&lt;br /&gt;
After the 4.0 version, the Moodle app automatically creates two link handlers for module plugins, you don&#039;t need to create them in your plugin&#039;s Javascript code anymore:&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginanme/view.php?id=X&#039;&#039;. When this link is clicked, it will open your module in the app.&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginname/index.php?id=X&#039;&#039;. When this link is clicked, it will open a page in the app listing all the modules of your type inside a certain course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Link handlers have some advanced features that allow you to change how links behave under different conditions.&lt;br /&gt;
======Patterns======&lt;br /&gt;
You can define a Regular Expression pattern to match certain links. This will apply the handler only to links that match the pattern.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = RegExp(&#039;\/mod\/foo\/specialpage.php&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Priority======&lt;br /&gt;
Multiple link handlers may apply to a given link. You can define the order of precedence by setting the priority; the handler with the highest priority will be used.&lt;br /&gt;
&lt;br /&gt;
All default handlers have a priority of 0, so 1 or higher will override the default.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Multiple actions======&lt;br /&gt;
Once a link has been matched, the handler&#039;s &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method determines what the link should do. This method has access to the URL and its parameters.&lt;br /&gt;
&lt;br /&gt;
Different actions can be returned depending on different conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                action: function(siteId, navCtrl) {&lt;br /&gt;
                    // The actual behaviour of the link goes here.&lt;br /&gt;
                },&lt;br /&gt;
                sites: [&lt;br /&gt;
                    // ...&lt;br /&gt;
                ],&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                // ...&lt;br /&gt;
            },&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once handlers have been matched for a link, the actions will be fetched for all the matching handlers, in priorty order. The first valid action will be used to open the link.&lt;br /&gt;
&lt;br /&gt;
If your handler is matched with a link, but a condition assessed in the &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method means you want to revert to the next highest priorty handler, you can invalidate your action by settings its sites propety to an empty array.&lt;br /&gt;
======Complex example======&lt;br /&gt;
This will match all URLs containing &amp;lt;code&amp;gt;/mod/foo/&amp;lt;/code&amp;gt;, and force those with an id parameter that&#039;s not in the &amp;lt;code&amp;gt;supportedModFoos&amp;lt;/code&amp;gt; array to open in the user&#039;s browser, rather than the app.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const that = this;&lt;br /&gt;
const supportedModFoos = [...];&lt;br /&gt;
&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = new RegExp(&#039;\/mod\/foo\/&#039;);&lt;br /&gt;
        this.name = &#039;AddonModFooLinkHandler&#039;;&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {     &lt;br /&gt;
        const action = {&lt;br /&gt;
            action() {&lt;br /&gt;
                that.CoreUtilsProvider.openInBrowser(url);&lt;br /&gt;
            },&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (supportedModFoos.indexOf(parseInt(params.id)) !== -1) {&lt;br /&gt;
            action.sites = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return [action];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreContentLinksDelegate.registerHandler(new AddonModFooLinkHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Module prefetch handler=====&lt;br /&gt;
The &amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt; handler allows you to define a list of offline functions to prefetch a module. However, you might want to create your own prefetch handler to determine what needs to be downloaded. For example, you might need to chain WS calls (pass the result of a WS call to the next one), and this cannot be done using offline functions.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to create a prefetch handler using the initialisation JS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Create a class that extends from CoreCourseActivityPrefetchHandlerBase.&lt;br /&gt;
class AddonModCertificateModulePrefetchHandler extends CoreCourseActivityPrefetchHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.name = &#039;AddonModCertificateModulePrefetchHandler&#039;;&lt;br /&gt;
        this.modName = &#039;certificate&#039;;&lt;br /&gt;
&lt;br /&gt;
        // This must match the plugin identifier from db/mobile.php,&lt;br /&gt;
        // otherwise the download link in the context menu will not update correctly.&lt;br /&gt;
        this.component = &#039;mod_certificate&#039;;&lt;br /&gt;
        this.updatesNames = /^configuration$|^.*files$/;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Override the prefetch call.&lt;br /&gt;
    prefetch(module, courseId, single, dirPath) {&lt;br /&gt;
        return this.prefetchPackage(module, courseId, single, prefetchCertificate);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prefetchCertificate(module, courseId, single, siteId) {&lt;br /&gt;
    // Perform all the WS calls.&lt;br /&gt;
    // You can access most of the app providers using that.ClassName. E.g. that.CoreWSProvider.call().&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseModulePrefetchDelegate.registerHandler(new AddonModCertificateModulePrefetchHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
One relatively simple full example is where you have a function that needs to work offline, but it has an additional argument other than the standard ones. You can imagine for this an activity like the book module, where it has multiple pages for the same &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;. The app will not automatically work with this situation — it will call the offline function with the standard arguments only — so you won&#039;t be able to prefetch all the possible parameters.&lt;br /&gt;
&lt;br /&gt;
To deal with this, you need to implement a web service in your Moodle component that returns the list of possible extra arguments, and then you can call this web service and loop around doing the same thing the app does when it prefetches the offline functions. Here is an example from a third-party module (showing only the actual prefetch function, the rest of the code is as above) where there are multiple values of a custom &amp;lt;code&amp;gt;section&amp;lt;/code&amp;gt; parameter for the mobile function &amp;lt;code&amp;gt;mobile_document_view&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function prefetchOucontent(module, courseId, single, siteId) {&lt;br /&gt;
    var component = &#039;mod_oucontent&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Get the site, first.&lt;br /&gt;
    return that.CoreSitesProvider.getSite(siteId).then(function(site) {&lt;br /&gt;
        // Read the list of pages in this document using a web service.&lt;br /&gt;
        return site.read(&#039;mod_oucontent_get_page_list&#039;, {&#039;cmid&#039;: module.id}).then(function(response) {&lt;br /&gt;
            var promises = [];&lt;br /&gt;
&lt;br /&gt;
            // For each page, read and process the page - this is a copy of logic in the app at&lt;br /&gt;
            // siteplugins.ts (prefetchFunctions), but modified to add the custom argument.&lt;br /&gt;
            for(var i = 0; i &amp;lt; response.length; i++) {&lt;br /&gt;
                var args = {&lt;br /&gt;
                    courseid: courseId,&lt;br /&gt;
                    cmid: module.id,&lt;br /&gt;
                    userid: site.getUserId()&lt;br /&gt;
                };&lt;br /&gt;
                if (response[i] !== &#039;&#039;) {&lt;br /&gt;
                    args.section = response[i];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                promises.push(that.CoreSitePluginsProvider.getContent(&lt;br /&gt;
                        component, &#039;mobile_document_view&#039;, args).then(&lt;br /&gt;
                        function(result) {&lt;br /&gt;
                            var subPromises = [];&lt;br /&gt;
                            if (result.files &amp;amp;&amp;amp; result.files.length) {&lt;br /&gt;
                                subPromises.push(that.CoreFilepoolProvider.downloadOrPrefetchFiles(&lt;br /&gt;
                                        site.id, result.files, true, false, component, module.id));&lt;br /&gt;
                            }&lt;br /&gt;
                            return Promise.all(subPromises);&lt;br /&gt;
                        }));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(promises);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Single activity course format=====&lt;br /&gt;
In the following example, the value of &amp;lt;code&amp;gt;INIT_TEMPLATES[&#039;main&#039;]&amp;lt;/code&amp;gt; is:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;componentClass&amp;quot; [data]=&amp;quot;data&amp;quot;&amp;gt;&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This template is returned by the initialisation method. And this is the JavaScript code returned:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatComponent {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.data = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ngOnChanges(changes) {&lt;br /&gt;
        var self = this;&lt;br /&gt;
&lt;br /&gt;
        if (this.course &amp;amp;&amp;amp; this.sections &amp;amp;&amp;amp; this.sections.length) {&lt;br /&gt;
            var module = this.sections[0] &amp;amp;&amp;amp; this.sections[0].modules &amp;amp;&amp;amp; this.sections[0].modules[0];&lt;br /&gt;
            if (module &amp;amp;&amp;amp; !this.componentClass) {&lt;br /&gt;
                that.CoreCourseModuleDelegate.getMainComponent(that.Injector, this.course, module).then((component) =&amp;gt; {&lt;br /&gt;
                    self.componentClass = component || that.CoreCourseUnsupportedModuleComponent;&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.data.courseId = this.course.id;&lt;br /&gt;
            this.data.module = module;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    doRefresh(refresher, done) {&lt;br /&gt;
        return Promise.resolve(this.dynamicComponent.callComponentFunction(&amp;quot;doRefresh&amp;quot;, [refresher, done]));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatHandler {&lt;br /&gt;
    &lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.name = &#039;singleactivity&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    isEnabled() {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canViewAllSections() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseTitle(course, sections) {&lt;br /&gt;
        if (sections &amp;amp;&amp;amp; sections[0] &amp;amp;&amp;amp; sections[0].modules &amp;amp;&amp;amp; sections[0].modules[0]) {&lt;br /&gt;
            return sections[0].modules[0].name;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return course.fullname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displayEnableDownload() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displaySectionSelector() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseFormatComponent() {&lt;br /&gt;
        return that.CoreCompileProvider.instantiateDynamicComponent(that.INIT_TEMPLATES[&#039;main&#039;], AddonSingleActivityFormatComponent);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseFormatDelegate.registerHandler(new AddonSingleActivityFormatHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using the JavaScript API===&lt;br /&gt;
The JavaScript API is only supported by the delegates specified in the [[#Templates_downloaded_on_login_and_rendered_using_JS_data_2|Templates downloaded on login and rendered using JS data]] section. This API allows you to override any of the functions of the default handler.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; specified in a handler registered in the &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; will be called immediately after the initialisation method, and the JavaScript returned by this method will be run. If this JavaScript code returns an object with certain functions, these functions will override the ones in the default handler.&lt;br /&gt;
&lt;br /&gt;
For example, if the JavaScript returned by the method returns something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The the &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function of the default handler will be overridden by the returned &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
The default handler for &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; only has 2 functions: &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt;. In addition, the JavaScript code can return an extra function named &amp;lt;code&amp;gt;componentInit&amp;lt;/code&amp;gt; that will be executed when the component returned by &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; is initialised.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to support the text user profile field using this API:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
var result = {&lt;br /&gt;
    componentInit: function() {&lt;br /&gt;
        if (this.field &amp;amp;&amp;amp; this.edit &amp;amp;&amp;amp; this.form) {&lt;br /&gt;
            this.field.modelName = &#039;profile_field_&#039; + this.field.shortname;&lt;br /&gt;
&lt;br /&gt;
            if (this.field.param2) {&lt;br /&gt;
                this.field.maxlength = parseInt(this.field.param2, 10) || &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.field.inputType = that.CoreUtilsProvider.isTrueOrOne(this.field.param3) ? &#039;password&#039; : &#039;text&#039;;&lt;br /&gt;
&lt;br /&gt;
            var formData = {&lt;br /&gt;
                value: this.field.defaultdata,&lt;br /&gt;
                disabled: this.disabled,&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            this.form.addControl(this.field.modelName,&lt;br /&gt;
                that.FormBuilder.control(formData, this.field.required &amp;amp;&amp;amp; !this.field.locked ? that.Validators.required : null));&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        var name = &#039;profile_field_&#039; + field.shortname;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            type: &amp;quot;text&amp;quot;,&lt;br /&gt;
            name: name,&lt;br /&gt;
            value: that.CoreTextUtilsProvider.cleanTags(formValues[name]),&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Translate dynamic strings===&lt;br /&gt;
If you wish to have an element that displays a localised string based on value from your template you can doing something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_myactivity.&amp;lt;% status %&amp;gt;&#039; | translate }}&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This could save you from having to write something like when only one value should be displayed:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        &amp;lt;%#isedting%&amp;gt;{{ &#039;plugin.mod_myactivity.editing&#039; | translate }}&amp;lt;%/isediting%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isopen%&amp;gt;{{ &#039;plugin.mod_myactivity.open&#039; | translate }}&amp;lt;%/isopen%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isclosed%&amp;gt;{{ &#039;plugin.mod_myactivity.closed&#039; | translate }}&amp;lt;%/isclosed%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using strings with dates===&lt;br /&gt;
If you have a string that you wish to pass a formatted date, for example in the Moodle language file you have:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;strwithdate&#039;] = &#039;This string includes a date of {$a-&amp;gt;date} in the middle of it.&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can localise the string correctly in your template using something like the following:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{ &#039;plugin.mod_myactivity.strwithdate&#039; | translate: {$a: { date: &amp;lt;% timestamp %&amp;gt; * 1000 | coreFormatDate: &amp;quot;dffulldate&amp;quot; } } }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A Unix timestamp must be multiplied by 1000 as the Mobile App expects millisecond timestamps, whereas Unix timestamps are in seconds.&lt;br /&gt;
===Support push notification clicks===&lt;br /&gt;
If your plugin sends push notifications to the app, you might want to open a certain page in the app when the notification is clicked. There are several ways to achieve this.&lt;br /&gt;
&lt;br /&gt;
The easiest way is to include a &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; in your notification. When the notification is clicked, the app will try to open the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; will also be displayed in web. If you want to use a specific URL for the app, different than the one displayed in web, you can do so by returning a &amp;lt;code&amp;gt;customdata&amp;lt;/code&amp;gt; array that contains an &amp;lt;code&amp;gt;appurl&amp;lt;/code&amp;gt; property:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$notification-&amp;gt;customdata = [&lt;br /&gt;
    &#039;appurl&#039; =&amp;gt; $myurl-&amp;gt;out(),&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In both cases you will have to create a link handler to treat the URL. For more info on how to create the link handler, please see [[#Advanced_link_handler|how to create an advanced link handler]].&lt;br /&gt;
&lt;br /&gt;
If you want to do something that only happens when the notification is clicked, not when the link is clicked, you&#039;ll have to implement a push click handler yourself. The way to create it is similar to [[#Advanced_link_handler|creating an advanced link handler]], but you&#039;ll have to use &amp;lt;code&amp;gt;CorePushNotificationsDelegate&amp;lt;/code&amp;gt; and your handler will have to implement the properties and functions defined in the [https://github.com/moodlehq/moodleapp/blob/master/src/core/features/pushnotifications/services/push-delegate.ts#L27 CorePushNotificationsClickHandler] interface.&lt;br /&gt;
===Implement a module similar to mod_label===&lt;br /&gt;
In Moodle 3.8 or higher, if your plugin doesn&#039;t support &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt; and you don&#039;t specify a &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; then the module will only display the module description in the course page and it won&#039;t be clickable in the app, just like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt;. You can decide if you want the module icon to be displayed or not (if you don&#039;t want it to be displayed, then don&#039;t define it in &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
However, if your plugin needs to work in previous versions of Moodle or you want to display something different than the description then you need a different approach.&lt;br /&gt;
&lt;br /&gt;
If your plugin wants to render something in the course page instead of just the module name and description you should specify the &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. The template returned by this method will be rendered in the course page. Please notice the HTML returned should not contain directives or components, only plain HTML.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want your module to be clickable then you just need to remove &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. With these 2 changes you can have a module that behaves like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt; in the app.&lt;br /&gt;
===Use Ionic navigation lifecycle functions===&lt;br /&gt;
Ionic let pages define some functions that will be called when certain navigation lifecycle events happen. For more info about these functions, see [https://ionicframework.com/docs/api/router-outlet Ionic&#039;s documentation].&lt;br /&gt;
&lt;br /&gt;
You can define these functions in your plugin javascript:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.ionViewWillLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In addition to that, you can also implement &amp;lt;code&amp;gt;canLeave&amp;lt;/code&amp;gt; to use Angular route guards:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.canLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So for example you can make your plugin ask for confirmation if the user tries to leave the page when he has some unsaved data.&lt;br /&gt;
===Module plugins: dynamically determine if a feature is supported===&lt;br /&gt;
In Moodle you can specify if your plugin supports a certain feature, like &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. If your plugin will always support or not a certain feature, then you can use the &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; to specify it ([[#Options_only_for_CoreCourseModuleDelegate|see more documentation about this]]). But if you need to calculate it dynamically then you will have to create a function to calculate it. &lt;br /&gt;
&lt;br /&gt;
This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section above). The JavaScript returned by your initialisation method will need to define a function named &amp;lt;code&amp;gt;supportsFeature&amp;lt;/code&amp;gt; that will receive the name of the feature:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    supportsFeature: function(featureName) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;.&lt;br /&gt;
== Testing ==&lt;br /&gt;
You can also write automated tests for your plugin using Behat, you can read more about it on the [[Acceptance testing for the Moodle App]] page.&lt;br /&gt;
== Upgrading plugins from an older version ==&lt;br /&gt;
If you added mobile support to your plugin for the Ionic 3 version of the app (previous to the 3.9.5 release), you will probably need to make some changes to make it compatible with Ionic 5.&lt;br /&gt;
&lt;br /&gt;
Learn more at the [[Moodle App Plugins Upgrade Guide]].&lt;br /&gt;
==Troubleshooting==&lt;br /&gt;
=== Invalid response received ===&lt;br /&gt;
You might receive this error when using the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive or similar. By default, the app expects all Web Service calls to return an object, if your Web Service returns another type (string, boolean, etc.) then you need to specify it using the &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; attribute of the directive. For example, if your WS returns a boolean value, then you should specify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{typeExpected: &#039;boolean&#039;}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In a similar way, if your Web Service returns &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; you need to tell the app not to expect any result using &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{responseExpected: false}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS ===&lt;br /&gt;
Some directives allow you to specify a form id or name to send the data from the form to a certain WS. These directives look for HTML inputs to retrieve the data to send. However, &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; don&#039;t use HTML inputs, they simulate them, so the directive isn&#039;t going to find their data and so it won&#039;t be sent to the Web Service.&lt;br /&gt;
&lt;br /&gt;
There are 2 workarounds to fix this problem.&lt;br /&gt;
==== Sending the data manually ====&lt;br /&gt;
The first solution is to send the missing params manually using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; property. We will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input value in a variable, and this variable will be passed to the parameters. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a template like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group name=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, responses: responses}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Basically, you need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to the affected element (in this case, the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt;). You can put whatever name you want as the value, we used &amp;quot;responses&amp;quot;. With this, every time the user selects a radio button the value will be stored in a variable called &amp;quot;responses&amp;quot;. Then, in the button we are passing this variable to the parameters of the Web Service.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute has priority over &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;, so if you have an input with &amp;lt;code&amp;gt;name=&amp;quot;responses&amp;quot;&amp;lt;/code&amp;gt; it will override what you&#039;re manually passing to &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;.&lt;br /&gt;
==== Using a hidden input ====&lt;br /&gt;
Since the directive is looking for HTML inputs, you need to add one with the value to send to the server. You can use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to synchronise your radio/checkbox/select with the new hidden input. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a radio button like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot; [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;hidden&amp;quot; [ngModel]=&amp;quot;responses&amp;quot; name=&amp;quot;responses&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we&#039;re using a variable called &amp;quot;responses&amp;quot; to synchronise the data between the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt; and the hidden input. You can use whatever name you want.&lt;br /&gt;
=== I can&#039;t return an object or array in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; ===&lt;br /&gt;
If you try to return an object or an array in any field inside &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, the Web Service call will fail with the following error:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Scalar type expected, array or object received&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Each field in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; must be a string, number or boolean; it cannot be an object or array. To make it work, you need to encode your object or array into a JSON string:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($data)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app will automatically parse this JSON and convert it back into an array or object.&lt;br /&gt;
==Examples==&lt;br /&gt;
===Accepting dynamic names in a Web Service===&lt;br /&gt;
We want to display a form where the names of the fields are dynamic, like it happens in quiz. This data will be sent to a new Web Service that we have created.&lt;br /&gt;
&lt;br /&gt;
The first issue we find is that the Web Service needs to define the names of the parameters received, but in this case they&#039;re dynamic. The solution is to accept an array of objects with name and value. So in the &amp;lt;code&amp;gt;_parameters()&amp;lt;/code&amp;gt; function of our new Web Service, we will add this parameter:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;data&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
     new external_single_structure(&lt;br /&gt;
        [&lt;br /&gt;
            &#039;name&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data name&#039;),&lt;br /&gt;
            &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data value&#039;),&lt;br /&gt;
        ]&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;The data to be saved&#039;, VALUE_DEFAULT, []&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now we need to adapt our form to send the data as the Web Service requires it. In our template, we have a button with the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive that will send the form data to our Web Service. To make this work we will have to pass the parameters manually, without using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute, because we need to format the data before it is sent.&lt;br /&gt;
&lt;br /&gt;
Since we will send the parameters manually and we want it all to be sent in the same array, we will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input data into a variable that we&#039;ll call &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;, but you can use the name you want. This variable will be an object that will hold the input data with the format &amp;quot;name-&amp;gt;value&amp;quot;. For example, if I have an input with name &amp;quot;a1&amp;quot; and value &amp;quot;My answer&amp;quot;, the data object will be:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{a1: &#039;My answer&#039;}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So we need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to all the inputs whose values need to be sent to the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; WS param. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-input name=&amp;quot;&amp;lt;% name %&amp;gt;&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.data[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re using &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; to store the data. We do it like this because we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the form, setting the values the user has already stored. If you don&#039;t need to initialise the form, then you can use the &amp;lt;code&amp;gt;dataObject&amp;lt;/code&amp;gt; variable, an empty object that the mobile app creates for you:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[(ngModel)]=&amp;quot;dataObject[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app has a function that allows you to convert this data object into an array like the one the WS expects: &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt;. So in our button we&#039;ll use this function to format the data before it&#039;s sent:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_ws_name&amp;quot;&lt;br /&gt;
    [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, data: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.data, &#039;name&#039;, &#039;value&#039;)}&amp;quot;&lt;br /&gt;
    successMessage&lt;br /&gt;
    refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see in the example above, we&#039;re specifying that the keys of the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object need to be stored in a property called &amp;quot;name&amp;quot;, and the values need to be stored in a property called &amp;quot;value&amp;quot;. If your Web Service expects different names you need to change the parameters of the &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
If you open your plugin now in the app it will display an error in the JavaScript console. The reason is that the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; variable doesn&#039;t exist inside &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. As it is explained in previous sections, &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; holds the data that you return in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; for your method. We&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the values to be displayed in the form.&lt;br /&gt;
&lt;br /&gt;
If the user hasn&#039;t answered the form yet, we can initialise the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object as an empty object. Please remember that we cannot return arrays or objects in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, so we&#039;ll return a JSON string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; &#039;{}&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
With the code above, the form will always be empty when the user opens it. But now we want to check if the user has already answered the form and fill the form with the previous values. We will do it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$userdata = get_user_responses(); // It will held the data in a format name-&amp;gt;value. Example: [&#039;a1&#039; =&amp;gt; &#039;My value&#039;].&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($userdata)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now the user will be able to see previous values when the form is opened, and clicking the button will send the data to our Web Service in array format.&lt;br /&gt;
==Moodle plugins with mobile support==&lt;br /&gt;
* Group choice: [https://moodle.org/plugins/mod_choicegroup Moodle plugins directory entry] and [https://github.com/ndunand/moodle-mod_choicegroup code in github].&lt;br /&gt;
* Custom certificate: [https://moodle.org/plugins/mod_customcert Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_customcert code in github].&lt;br /&gt;
* Gapfill question type: [https://moodle.org/plugins/qtype_gapfill Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_gapfill in github].&lt;br /&gt;
* Wordselect question type: [https://moodle.org/plugins/qtype_wordselect Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_wordselect in github].&lt;br /&gt;
* RegExp question type: [https://moodle.org/plugins/qtype_regexp Moodle plugins directory entry] and [https://github.com/rezeau/moodle-qtype_regexp in github].&lt;br /&gt;
* Certificate: [https://moodle.org/plugins/mod_certificate Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_certificate in github].&lt;br /&gt;
* Attendance [https://moodle.org/plugins/mod_attendance Moodle plugins directory entry] and [https://github.com/danmarsden/moodle-mod_attendance in github].&lt;br /&gt;
* ForumNG (unfinished support) [https://moodle.org/plugins/mod_forumng Moodle plugins directory entry] and [https://github.com/moodleou/moodle-mod_forumng in github].&lt;br /&gt;
* News block [https://github.com/moodleou/moodle-block_news in github].&lt;br /&gt;
* H5P activity module [https://moodle.org/plugins/mod_hvp Moodle plugins directory entry] and [https://github.com/h5p/h5p-moodle-plugin in github].&lt;br /&gt;
See the complete list in [https://moodle.org/plugins/browse.php?list=award&amp;amp;id=6 the plugins database] (it may contain some outdated plugins).&lt;br /&gt;
=== Mobile app support award ===&lt;br /&gt;
If you want your plugin to be awarded in the plugins directory and marked as supporting the mobile app, please feel encouraged to contact us via email at [mailto:mobile@moodle.com mobile@moodle.com].&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to include a link to your plugin page and the location of its code repository.&lt;br /&gt;
&lt;br /&gt;
See [https://moodle.org/plugins/?q=award:mobile-app the list of awarded plugins] in the plugins directory.&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Moodle App Ionic 5]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62061</id>
		<title>Moodle App Plugins Development Guide</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62061"/>
		<updated>2022-04-25T08:26:45Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: /* Options only for AddonMessageOutputDelegate */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle App (Ionic 5)}}&lt;br /&gt;
If you want to add mobile support to your Moodle plugin, you can achieve it by extending different areas of the app using &#039;&#039;just PHP server side code&#039;&#039; and providing templates written with [https://ionicframework.com/docs/components Ionic] and custom components.&lt;br /&gt;
&lt;br /&gt;
You will have to:&lt;br /&gt;
# Create a &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file in your plugin. In this file, you will be able to indicate which areas of the app you want to extend. For example, adding a new option in the main menu, implementing support for a new activity module, including a new option in the course menu, including a new option in the user profile, etc. All the areas supported are described further in this document.&lt;br /&gt;
# Create new functions in a reserved namespace that will return the content of the new options. The content should be returned rendered as html. This html should use Ionic components so that it looks native, but it can be generated using mustache templates.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let’s clarify some points:&lt;br /&gt;
* You don’t need to create new Web Service functions (although you will be able to use them for advanced features). You just need plain php functions that will be placed in a reserved namespace.&lt;br /&gt;
* Those functions will be exported via the Web Service function &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt;.&lt;br /&gt;
* As arguments of your functions you will always receive the &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, some relevant details of the app (like the app version or the current language in the app), and some specific data depending on the type of plugin (&amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
* The mobile app also implements a list of custom Ionic components and directives that provide dynamic behaviour; like indicating that you are linking a file that can be downloaded, allowing a transition to new pages into the app calling a specific function in the server, submitting form data to the server, etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Getting started==&lt;br /&gt;
If you only want to write a plugin, it is not necessary that you set up your environment to work with the Moodle App. In fact, you don&#039;t even need to compile it. You can just [[Using the Moodle App in a browser|use a Chromium-based browser]] to add mobile support to your plugins!&lt;br /&gt;
&lt;br /&gt;
You can use the app from one of the hosted versions on [https://master.apps.moodledemo.net master.apps.moodledemo.net] (the latest stable version) and [https://integration.apps.moodledemo.net integration.apps.moodledemo.net] (the latest development version). If you need any specific environment (hosted versions are deployed with a &#039;&#039;production&#039;&#039; environment), you can also use [[Moodle App Docker Images|Docker images]]. And if you need to test your plugin in a native device, you can always use [https://download.moodle.org/mobile Moodle HQ&#039;s application].&lt;br /&gt;
&lt;br /&gt;
This should suffice for developing plugins. However, if you are working on advanced functionality and you need to run the application from the source code, you can find more information in the [[Moodle App Development Guide]].&lt;br /&gt;
===Development workflow===&lt;br /&gt;
Before getting into the specifics of your plugin, we recommend that you start adding a simple &amp;quot;Hello World&amp;quot; button in the app to see that everything works properly.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s say your plugin is called &amp;lt;code&amp;gt;local_hello&amp;lt;/code&amp;gt;, you can start by adding the following files:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;local_hello&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [&lt;br /&gt;
            &#039;hello&#039; =&amp;gt; [&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreMainMenuDelegate&#039;,&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;view_hello&#039;,&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;title&#039; =&amp;gt; &#039;hello&#039;,&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; &#039;earth&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [&lt;br /&gt;
            [&#039;hello&#039;, &#039;local_hello&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace local_hello\output;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    public static function view_hello() {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; &#039;&amp;lt;h1 class=&amp;quot;text-center&amp;quot;&amp;gt;{{ &amp;quot;plugin.local_hello.hello&amp;quot; | translate }}&amp;lt;/h1&amp;gt;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;lang/en/local_hello.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;hello&#039;] = &#039;Hello World&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once you&#039;ve done that, try logging into your site in the app and you should see a new button in the main menu or more menu (depending on the device) saying &amp;quot;Hello World&amp;quot;. If you press this button, you should see a page saying &amp;quot;Hello World!&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Congratualtions, you have written your first Moodle plugin with moodle support!&lt;br /&gt;
&lt;br /&gt;
You can read the rest of this page to learn more about mobile plugins and start working on your plugin. Here&#039;s some things to keep in mind:&lt;br /&gt;
* If you change the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file, you will have to refresh the browser. And remember to [https://developer.chrome.com/docs/devtools/network/reference/#disable-cache disable the network cache].&lt;br /&gt;
* If you change an existing template or function, you won’t have to refresh the browser. In most cases, doing a PTR (Pull To Refresh) in the page that displays the template will suffice.&lt;br /&gt;
* If any of these doesn&#039;t show your changes, you may need to [https://docs.moodle.org/311/en/Developer_tools#Purge_all_caches purge all caches] to avoid problems with the auto-loading cache.&lt;br /&gt;
* Ultimately, if that doesn&#039;t work either, you may have to log out from the site and log in again. If any changes affect plugin installation, you may also need to increase the version in your plugin&#039;s &amp;lt;code&amp;gt;version.php&amp;lt;/code&amp;gt; file and upgrade it in the site.&lt;br /&gt;
==Types of plugins==&lt;br /&gt;
There are 3 types of plugins:&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
[[File:Templates_downloaded_when_requested.png|thumb]]&lt;br /&gt;
With this type of plugin, the template of your plugin will be generated and downloaded when the user opens the plugin in the app. This means that your function will receive some context parameters. For example, if you&#039;re developing a course module plugin you will receive the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; (course module ID). You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
[[File:Templates_downloaded_on_login.png|thumb]]&lt;br /&gt;
With this type of plugin, the template for your plugin will be downloaded when the user logs in into the app and will be stored in the device. This means that your function will not receive any context parameters, and you need to return a generic template that will be built with JS data like the ones in the Moodle App. When the user opens a page that includes your plugin, your template will receive the required JS data and your template will be rendered. You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
You can always implement the whole plugin yourself using JavaScript instead of using our API. In fact, this is required if you want to implement some features like capturing links in the Mobile app. You can see the list of delegates that only support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
==Step by step example==&lt;br /&gt;
In this example, we are going to update an existing plugin, the [https://github.com/mdjnelson/moodle-mod_certificate Certificate activity module], that previously used a [[Moodle Mobile 2 (Ionic 1) Remote add-ons|Remote add-on]] (a legacy approach to implement mobile plugins).&lt;br /&gt;
&lt;br /&gt;
This is a simple activity module that displays the certificate issued for the current user along with the list of the dates of previously issued certificates. It also stores in the course log that the user viewed a certificate. This module also works offline: when the user downloads the course or activity, the data is pre-fetched and can be viewed offline.&lt;br /&gt;
===Step 1. Update the &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file===&lt;br /&gt;
In this case, we are updating an existing file. For new plugins, you should create this new file.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;mod_certificate&#039; =&amp;gt; [ // Plugin identifier&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;coursecertificate&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/mod/certificate/pix/icon.gif&#039;,&lt;br /&gt;
                    &#039;class&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
       &lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseModuleDelegate&#039;, // Delegate (where to display the link to the plugin)&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;mobile_course_view&#039;, // Main function in \mod_certificate\output\mobile&lt;br /&gt;
                &#039;offlinefunctions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;mobile_course_view&#039; =&amp;gt; [],&lt;br /&gt;
                    &#039;mobile_issues_view&#039; =&amp;gt; [],&lt;br /&gt;
                ], // Function that needs to be downloaded for offline.&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [ // Language strings that are used in all the handlers.&lt;br /&gt;
            [&#039;pluginname&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;summaryofattempts&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;getcertificate&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;requiredtimenotmet&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;viewcertificateviews&#039;, &#039;certificate&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Plugin identifier:&lt;br /&gt;
: A unique name for the plugin, it can be anything (there’s no need to match the module name).&lt;br /&gt;
&lt;br /&gt;
;Handlers (Different places where the plugin will display content):&lt;br /&gt;
: A plugin can be displayed in different views in the app. Each view should have a unique name inside the plugin scope (alphanumeric).&lt;br /&gt;
&lt;br /&gt;
; Display data:&lt;br /&gt;
: This is only needed for certain types of plugins. Also, depending on the type of delegate it may require additional (or less fields). In this case, we are indicating the module icon.&lt;br /&gt;
&lt;br /&gt;
; Delegate&lt;br /&gt;
: Where to display the link to the plugin, see the [[#Delegates|Delegates]] section for all the possible options.&lt;br /&gt;
&lt;br /&gt;
; Method:&lt;br /&gt;
: This is the method in the Moodle &amp;lt;code&amp;gt;\{component-name}\output\mobile&amp;lt;/code&amp;gt; class to be executed the first time the user clicks in the new option displayed in the app.&lt;br /&gt;
&lt;br /&gt;
; Offlinefunctions&lt;br /&gt;
: This is the list of functions that need to be called and stored when the user downloads a course for offline usage. Please note that you can add functions here that are not even listed in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. &lt;br /&gt;
: In our example, downloading for offline access will mean that we&#039;ll execute the functions for getting the certificate and issued certificates passing as parameters the current &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; (and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; when we are using the mod or course delegate). If we have the result of those functions stored in the app, we&#039;ll be able to display the certificate information even if the user is offline.&lt;br /&gt;
: Offline functions will be mostly used to display information for final users, any further interaction with the view won’t be supported offline (for example, trying to send information when the user is offline).&lt;br /&gt;
: You can indicate here other Web Services functions, indicating the parameters that they might need from a defined subset (currently &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Prefetching the module will also download all the files returned by the methods in these offline functions (in the &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; array).&lt;br /&gt;
: Note that if your functions use additional custom parameters (for example, if you implement multiple pages within a module&#039;s view function by using a &amp;lt;code&amp;gt;page&amp;lt;/code&amp;gt; parameter in addition to the usual &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;) then the app will not know which additional parameters to supply. In this case, do not list the function in &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt;; instead, you will need to manually implement a [[#Module_prefetch_handler|module prefetch handler]].&lt;br /&gt;
&lt;br /&gt;
;Lang:&lt;br /&gt;
: The language pack string ids used in the plugin by all the handlers. Normally these will be strings from your own plugin, however, you can list any strings you need here, like &amp;lt;code&amp;gt;[&#039;cancel&#039;, &#039;moodle&#039;]&amp;lt;/code&amp;gt;. If you do this, be warned that in the app you will then need to refer to that string as &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.myplugin.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (not &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.moodle.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Please only include the strings you actually need. The Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform, and this will then be cached, so listing too many strings is very wasteful.&lt;br /&gt;
There are additional attributes supported by the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; list, you can find about them in the [[#Mobile.php_supported_options|Mobile.php supported options]] section.&lt;br /&gt;
===Step 2. Creating the main function===&lt;br /&gt;
The main function displays the current issued certificate (or several warnings if it’s not possible to issue a certificate). It also displays a link to view the dates of previously issued certificates.&lt;br /&gt;
&lt;br /&gt;
All the functions must be created in the plugin or subsystem &amp;lt;code&amp;gt;classes/output&amp;lt;/code&amp;gt; directory, the name of the class must be &amp;lt;code&amp;gt;mobile&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
For this example, the namespace name will be &amp;lt;code&amp;gt;mod_certificate\output&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace mod_certificate\output;&lt;br /&gt;
&lt;br /&gt;
use context_module;&lt;br /&gt;
use mod_certificate_external;&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Returns the certificate course view for the mobile app.&lt;br /&gt;
     *&lt;br /&gt;
     * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
     *&lt;br /&gt;
     * @return array       HTML, JS and other data.&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
        $args = (object) $args;&lt;br /&gt;
        $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
        // Capabilities check.&lt;br /&gt;
        require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
        $context = \context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
        require_capability(&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
        if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
            require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
        }&lt;br /&gt;
        $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
        // Get certificates from external (taking care of exceptions).&lt;br /&gt;
        try {&lt;br /&gt;
            $issued = \mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
            $certificates = \mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
            $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $issues = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Set timemodified for each certificate.&lt;br /&gt;
        foreach ($issues as $issue) {&lt;br /&gt;
            if (empty($issue-&amp;gt;timemodified)) {&lt;br /&gt;
                $issue-&amp;gt;timemodified = $issue-&amp;gt;timecreated;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $showget = true;&lt;br /&gt;
        if ($certificate-&amp;gt;requiredtime &amp;amp;&amp;amp; !has_capability(&#039;mod/certificate:manage&#039;, $context)) {&lt;br /&gt;
            if (certificate_get_course_time($certificate-&amp;gt;course) &amp;lt; ($certificate-&amp;gt;requiredtime * 60)) {&lt;br /&gt;
                $showget = false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $certificate-&amp;gt;name = format_string($certificate-&amp;gt;name);&lt;br /&gt;
        [$certificate-&amp;gt;intro, $certificate-&amp;gt;introformat] =&lt;br /&gt;
                external_format_text($certificate-&amp;gt;intro, $certificate-&amp;gt;introformat, $context-&amp;gt;id, &#039;mod_certificate&#039;, &#039;intro&#039;);&lt;br /&gt;
        $data = [&lt;br /&gt;
            &#039;certificate&#039; =&amp;gt; $certificate,&lt;br /&gt;
            &#039;showget&#039; =&amp;gt; $showget &amp;amp;&amp;amp; count($issues) &amp;gt; 0,&lt;br /&gt;
            &#039;issues&#039; =&amp;gt; $issues,&lt;br /&gt;
            &#039;issue&#039; =&amp;gt; $issues[0],&lt;br /&gt;
            &#039;numissues&#039; =&amp;gt; count($issues),&lt;br /&gt;
            &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
            &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;files&#039; =&amp;gt; $issues,&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Function declaration: &lt;br /&gt;
: The function name is the same as the one used in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file (&amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; field). There is only one argument, &amp;lt;code&amp;gt;$args&amp;lt;/code&amp;gt;, which is an array containing all the information sent by the mobile app (the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversionname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversioncode&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;applang&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appcustomurlscheme&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
&lt;br /&gt;
; Function implementation:&lt;br /&gt;
: In the first part of the function, we check permissions and capabilities (like a &amp;lt;code&amp;gt;view.php&amp;lt;/code&amp;gt; script would do normally). Then we retrieve the certificate information that’s necessary to display the template.&lt;br /&gt;
&lt;br /&gt;
; Function return:&lt;br /&gt;
* &amp;lt;code&amp;gt;templates&amp;lt;/code&amp;gt; — The rendered template (notice that we could return more than one template, but we usually would only need one). By default the app will always render the first template received, the rest of the templates can be used if the plugin defines some JavaScript code.&lt;br /&gt;
* &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; — Empty, because we don’t need any in this case.&lt;br /&gt;
* &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; — Empty as well, because we don’t need any additional data to be used by directives or components in the template. This field will be published as an object supporting 2-way data-binding in the template.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; — A list of files that the app should be able to download (for offline usage mostly).&lt;br /&gt;
===Step 3. Creating the template for the main function===&lt;br /&gt;
This is the most important part of your plugin because it contains the code that will be rendered on the mobile app.&lt;br /&gt;
&lt;br /&gt;
In this template we’ll be using Ionic, together with directives and components specific to the Moodle App.&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;ion-&amp;lt;/code&amp;gt; are ionic components. Most of the time, the component name is self-explanatory but you may refer to a detailed guide here: https://ionicframework.com/docs/components/&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;core-&amp;lt;/code&amp;gt; are custom components of the Moodle App.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_page.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;core-course-module-description description=&amp;quot;&amp;lt;% certificate.intro %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-course-module-description&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;ion-list-header&amp;gt;&lt;br /&gt;
            &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.summaryofattempts&#039; | translate }}&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-list-header&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-new-content title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; &lt;br /&gt;
                        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
                        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
                    {{ &#039;plugin.mod_certificate.viewcertificateviews&#039; | translate: {$a: &amp;lt;% numissues %&amp;gt;} }}&lt;br /&gt;
                &amp;lt;/ion-button&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&lt;br /&gt;
                        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
                        [files]=&amp;quot;[{&lt;br /&gt;
                            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
                            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
                            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
                        }]&amp;quot;&amp;gt;&lt;br /&gt;
                 &lt;br /&gt;
                        &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot; slot=&amp;quot;start&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                            {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
                    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%^showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;p&amp;gt;{{ &#039;plugin.mod_certificate.requiredtimenotmet&#039; | translate }}&amp;lt;/p&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;!-- Call log WS when the template is loaded. --&amp;gt;&lt;br /&gt;
        &amp;lt;span core-site-plugins-call-ws-on-load name=&amp;quot;mod_certificate_view_certificate&amp;quot;&lt;br /&gt;
                [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/span&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache).&lt;br /&gt;
&lt;br /&gt;
Then we display the module description using &amp;lt;code&amp;gt;core-course-module-description&amp;lt;/code&amp;gt;, which is a component used to include the course module description.&lt;br /&gt;
&lt;br /&gt;
For displaying the certificate information we create a list of elements, adding a header on top.&lt;br /&gt;
&lt;br /&gt;
The following line using the &amp;lt;code&amp;gt;translate&amp;lt;/code&amp;gt; filter indicates that the app will translate the &amp;lt;code&amp;gt;summaryofattempts&amp;lt;/code&amp;gt; string id (here we could’ve used mustache translation but it is usually better to delegate the strings translations to the app). The string id has the following format: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
plugin.{plugin-identifier}.{string-id}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Where &amp;lt;code&amp;gt;{plugin-identifier}&amp;lt;/code&amp;gt; is taken from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;{string-id}&amp;lt;/code&amp;gt; must be indicated in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; field in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Then, we display a button to transition to another page if there are certificates issued. The attribute (directive) &amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt; indicates that if the user clicks the button, we need to call the &amp;lt;code&amp;gt;mobile_issues_view&amp;lt;/code&amp;gt; function in the &amp;lt;code&amp;gt;mod_certificate&amp;lt;/code&amp;gt; component; passing as arguments the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;. The content returned by this function will be displayed in a new page (read the following section to see the code of this new page).&lt;br /&gt;
&lt;br /&gt;
Just after this button, we display another one but this time for downloading an issued certificate. The &amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt; directive indicates that clicking this button is for downloading the whole activity and opening the main file. This means that, when the user clicks this button, the whole certificate activity will be available offline.&lt;br /&gt;
&lt;br /&gt;
Finally, just before the &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt; is closed, we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt; directive to indicate that once the page is loaded, we need to call a Web Service function in the server, in this case we are calling the &amp;lt;code&amp;gt;mod_certificate_view_certificate&amp;lt;/code&amp;gt; that will log that the user viewed this page.&lt;br /&gt;
&lt;br /&gt;
As you can see, no JavaScript was necessary at all. We used plain HTML elements and attributes that did all the complex dynamic logic (like calling a Web Service) behind the scenes.&lt;br /&gt;
===Step 4. Adding an additional page===&lt;br /&gt;
Add the following method to &amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Returns the certificate issues view for the mobile app.&lt;br /&gt;
 * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
 *&lt;br /&gt;
 * @return array       HTML, JS and other data.&lt;br /&gt;
 */&lt;br /&gt;
public static function mobile_issues_view($args) {&lt;br /&gt;
    global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
    $args = (object) $args;&lt;br /&gt;
    $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
    // Capabilities check.&lt;br /&gt;
    require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
    $context = context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
    require_capability (&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
    if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
        require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
    }&lt;br /&gt;
    $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
    // Get certificates from external (taking care of exceptions).&lt;br /&gt;
    try {&lt;br /&gt;
        $issued = mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
        $certificates = mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
        $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
    } catch (Exception $e) {&lt;br /&gt;
        $issues = [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $data = [&#039;issues&#039; =&amp;gt; $issues];&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
            [&lt;br /&gt;
                &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_issues&#039;, $data),&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
        &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This method for the new page was added just after &amp;lt;code&amp;gt;mobile_course_view&amp;lt;/code&amp;gt;, the code is quite similar: checks the capabilities, retrieves the information required for the template, and returns the template rendered.&lt;br /&gt;
&lt;br /&gt;
The code of the mustache template is also very simple.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_issues.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &amp;lt;%timecreated%&amp;gt; | coreToLocaleString }}&amp;lt;/p&amp;gt;&lt;br /&gt;
                    &amp;lt;p&amp;gt;&amp;lt;%grade%&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As we did in the previous template, in the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache). &lt;br /&gt;
&lt;br /&gt;
Here we are creating an Ionic list that will display a new item in the list per each issued certificated.&lt;br /&gt;
&lt;br /&gt;
For the issued certificated we’ll display the time when it was created (using the app filter &amp;lt;code&amp;gt;coreToLocaleString&amp;lt;/code&amp;gt;). We are also displaying the grade displayed in the certificate (if any).&lt;br /&gt;
===Step 5. Plugin webservices, if included===&lt;br /&gt;
If your plugin uses its own web services, they will also need to be enabled for mobile access in your &amp;lt;code&amp;gt;db/services.php&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
The following line should be included in each webservice definition:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;services&#039; =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/db/services.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$functions = [&lt;br /&gt;
    &#039;mod_certificate_get_certificates_by_courses&#039; =&amp;gt; [&lt;br /&gt;
        &#039;classname&#039;     =&amp;gt; &#039;mod_certificate_external&#039;,&lt;br /&gt;
        &#039;methodname&#039;    =&amp;gt; &#039;get_certificates_by_courses&#039;,&lt;br /&gt;
        &#039;description&#039;   =&amp;gt; &#039;Returns a list of certificate instances...&#039;,&lt;br /&gt;
        &#039;type&#039;          =&amp;gt; &#039;read&#039;,&lt;br /&gt;
        &#039;capabilities&#039;  =&amp;gt; &#039;mod/certificate:view&#039;,&lt;br /&gt;
        &#039;services&#039;      =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Mobile.php supported options==&lt;br /&gt;
In the previous section, we learned about some of the existing options for handlers configuration. This is the full list of supported options.&lt;br /&gt;
===Common options===&lt;br /&gt;
* &amp;lt;code&amp;gt;delegate&amp;lt;/code&amp;gt; (mandatory) — Name of the delegate to register the handler in.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (mandatory) — The method to call to retrieve the main page content.&lt;br /&gt;
* &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; (optional) — A method to call to retrieve the initialisation JS and the restrictions to apply to the whole handler. It can also return templates that can be used from JavaScript. You can learn more about this in the [[#Initialisation|Initialisation]] section.&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttocurrentuser&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForUser&amp;lt;/code&amp;gt; function. If true, the handler will only be shown for the current user. For more info about displaying the plugin only for certain users, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttoenrolledcourses&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForCourse&amp;lt;/code&amp;gt; function. If true or not defined, the handler will only be shown for courses the user is enrolled in. For more info about displaying the plugin only for certain courses, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;styles&amp;lt;/code&amp;gt; (optional) — An array with two properties: &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt;. The URL should point to a CSS file, either using an absolute URL or a relative URL. This file will be downloaded and applied by the app. It&#039;s recommended to include styles that will only affect your plugin templates. The version number is used to determine if the file needs to be downloaded again, you should change the version number everytime you change the CSS file.&lt;br /&gt;
* &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; (optional) — If your plugin supports a component in the app different than the one defined by your plugin, you can use this property to specify it. For example, you can create a local plugin to support a certain course format, activity, etc. The component of your plugin in Moodle would be &amp;lt;code&amp;gt;local_whatever&amp;lt;/code&amp;gt;, but in &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; you can specify that this handler will implement &amp;lt;code&amp;gt;format_whatever&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;mod_whatever&amp;lt;/code&amp;gt;. This property was introduced in the version 3.6.1 of the app.&lt;br /&gt;
===Options only for CoreMainMenuDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. Main Menu plugins are always displayed in the More tab, they cannot be displayed as tabs in the bottom bar.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreMainMenuHomeDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseOptionsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ismenuhandler&amp;lt;/code&amp;gt; (optional) — Supported from the 3.7.1 version of the app. Set it to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if you want your plugin to be displayed in the contextual menu of the course instead of in the top tabs. The contextual menu is displayed when you click in the 3-dots button at the top right of the course.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseModuleDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — Path to the module icon. After Moodle app 4.0, this icon is only used as a fallback, the app will always try to use the theme icon so themes can override icons in the app.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (optional) — The function to call to retrieve the main page content. In this delegate the method is optional. If the method is not set, the module won&#039;t be clickable.&lt;br /&gt;
* &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt; (optional) — List of functions to call when prefetching the module. It can be a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; method or a WS. You can filter the params received by the WS. By default, WS will receive these params: &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;. Other valid values that will be added if they are present in the list of params: &amp;lt;code&amp;gt;courseids&amp;lt;/code&amp;gt; (it will receive a list with the courses the user is enrolled in), &amp;lt;code&amp;gt;{component}id&amp;lt;/code&amp;gt; (For example, &amp;lt;code&amp;gt;certificateid&amp;lt;/code&amp;gt;).&lt;br /&gt;
* &amp;lt;code&amp;gt;downloadbutton&amp;lt;/code&amp;gt; (optional) — Whether to display download button in the module. If not defined, the button will be shown if there is any offlinefunction.&lt;br /&gt;
* &amp;lt;code&amp;gt;isresource&amp;lt;/code&amp;gt; (optional) — Whether the module is a resource or an activity. Only used if there is any offline function. If your module relies on the &amp;lt;code&amp;gt;contents&amp;lt;/code&amp;gt; field, then it should be &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;updatesnames&amp;lt;/code&amp;gt; (optional) — Only used if there is any offline function. A regular expression to check if there&#039;s any update in the module. It will be compared to the result of &amp;lt;code&amp;gt;core_course_check_updates&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayopeninbrowser&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Open in browser&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayOpenInBrowser = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydescription&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Description&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayDescription = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayrefresh&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Refresh&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayRefresh = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayprefetch&amp;lt;/code&amp;gt; (optional) — Whether the module should display the download option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayPrefetch = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysize&amp;lt;/code&amp;gt; (optional) — Whether the module should display the downloaded size in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displaySize = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; (optional) — It can be used to specify the supported features of the plugin. Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. It should be an array with features as keys (For example, &amp;lt;code&amp;gt;[FEATURE_NO_VIEW_LINK =&amp;gt; true&amp;lt;/code&amp;gt;). If you need to calculate this dynamically please see [[#Module_plugins:_dynamically_determine_if_a_feature_is_supported|Module plugins: dynamically determine if a feature is supported]]. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; (optional) — If set, this method will be called when the course is rendered and the HTML returned will be displayed in the course page for the module. Please notice the HTML returned should not contain directives or components, only default HTML. Supported from the 3.8 version of the app.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseFormatDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;canviewallsections&amp;lt;/code&amp;gt; (optional) — Whether the course format allows seeing all sections in a single page. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayenabledownload&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, it&#039;s no longer used.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysectionselector&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, use &#039;&#039;displaycourseindex&#039;&#039; instead.&lt;br /&gt;
*&amp;lt;code&amp;gt;displaycourseindex&amp;lt;/code&amp;gt; (optional) — Whether the default course index should be displayed. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
===Options only for CoreUserDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — The type of the addon. The values accepted are &amp;lt;code&amp;gt;&#039;newpage&#039;&amp;lt;/code&amp;gt; (default) and &amp;lt;code&amp;gt;&#039;communication&#039;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreSettingsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for AddonMessageOutputDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
**&amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreBlockDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (optional):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section. If this is not supplied, it will default to &amp;lt;code&amp;gt;&#039;plugins.block_{block-name}.pluginname&#039;&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class. If this is not supplied, it will default to &amp;lt;code&amp;gt;block_{block-name}&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — Possible values are:&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;title&amp;quot;&amp;lt;/code&amp;gt; — Your block will only display the block title, and when it&#039;s clicked it will open a new page to display the block contents (the template returned by the block&#039;s method).&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;prerendered&amp;quot;&amp;lt;/code&amp;gt; — Your block will display the content and footer returned by the WebService to get the blocks (for example, &amp;lt;code&amp;gt;core_block_get_course_blocks&amp;lt;/code&amp;gt;), so your block&#039;s method will never be called.&lt;br /&gt;
*** Any other value — Your block will immediately call the method specified in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and it will use the template to render the block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fallback&amp;lt;/code&amp;gt; (optional) — This option allows you to specify a block to use in the app instead of your block. For example, you can make the app display the &amp;quot;My overview&amp;quot; block instead of your block in the app by setting &amp;lt;code&amp;gt;&#039;fallback&#039; =&amp;gt; &#039;myoverview&#039;&amp;lt;/code&amp;gt;. The fallback will only be used if you don&#039;t specify a &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; is different to &amp;lt;code&amp;gt;&#039;title&#039;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&#039;prerendered&#039;&amp;lt;/code&amp;gt;. Supported from the 3.9.0 version of the app.&lt;br /&gt;
==Delegates==&lt;br /&gt;
Delegates can be classified by type of plugin. For more info about type of plugins, please see the [[#Types_of_plugins|Types of plugins]] section.&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new items to the main menu (currently displayed at the bottom of the app). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuHomeDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new tabs in the home page (by default the app is displaying the &amp;quot;Dashboard&amp;quot; and &amp;quot;Site home&amp;quot; tabs). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new options in a course (Participants or Grades are examples of this type of delegate).&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting activity modules or resources.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add additional options in the user profile page in the app.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting course formats. When you open a course from the course list in the mobile app, it will check if there is a &amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt; handler for the format that site uses. If so, it will display the course using that handler. Otherwise, it will use the default app course format.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile course formats]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreSettingsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to add a new option in the settings page.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonMessageOutputDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a message output plugin.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreBlockDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a block. For example, blocks can be displayed in Site Home, Dashboard and the Course page.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question types.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile question types]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionBehaviourDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question behaviours.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting user profile fields.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModQuizAccessRuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a quiz access rule.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModAssignSubmissionDelegate&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;AddonModAssignFeedbackDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use these delegates to support assign submission or feedback plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonWorkshopAssessmentStrategyDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a workshop assessment strategy plugin.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
These delegates require JavaScript to be supported. See [[#Initialisation|Initialisation]] for more information.&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreContentLinksDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreCourseModulePrefetchDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFileUploaderDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CorePluginFileDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFilterDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
==Available components and directives==&lt;br /&gt;
===Difference between components and directives===&lt;br /&gt;
A directive is usually represented as an HTML attribute, allows you to extend a piece of HTML with additional information or functionality. Example of directives are: &amp;lt;code&amp;gt;core-auto-focus&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*ngIf&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ng-repeat&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components are also directives, but they are usually represented as an HTML tag and they are used to add custom elements to the app. Example of components are &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-item&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;core-search-box&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components and directives are Angular concepts; you can learn more about them and the components come out of the box with Ionic in the following links:&lt;br /&gt;
* [https://angular.io/guide/built-in-directives Angular directives documentation]&lt;br /&gt;
* [https://ionicframework.com/docs/components Ionic components]&lt;br /&gt;
===Custom core components and directives===&lt;br /&gt;
These are some useful custom components and directives that are only available in the Moodle App. Please note that this isn’t the full list of custom components and directives, it’s just an extract of the most common ones.&lt;br /&gt;
&lt;br /&gt;
You can find a full list of components and directives in the source code of the app, within [https://github.com/moodlehq/moodleapp/tree/master/src/core/components &amp;lt;code&amp;gt;src/core/components&amp;lt;/code&amp;gt;] and [https://github.com/moodlehq/moodleapp/tree/master/src/core/directives &amp;lt;code&amp;gt;src/core/directives&amp;lt;/code&amp;gt;].&lt;br /&gt;
====&amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;====&lt;br /&gt;
This directive formats the text and adds some directives needed for the app to work as it should. For example, it treats all links and all the embedded media so they work fine in the app. If some content in your template includes links or embedded media, please use this directive.&lt;br /&gt;
&lt;br /&gt;
This directive automatically applies &amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt; to all the links and embedded media.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;text&amp;lt;/code&amp;gt; (string) — The text to format.&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;adaptImg&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to adapt images to screen width. &lt;br /&gt;
* &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether all HTML tags should be removed.&lt;br /&gt;
* &amp;lt;code&amp;gt;singleLine&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether new lines should be removed to display all the text in single line. Only if &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; (number) — Optional. Max height in pixels to render the content box. The minimum accepted value is 50. Using this parameter will force &amp;lt;code&amp;gt;display: block&amp;lt;/code&amp;gt; to calculate the height better. If you want to avoid this, use &amp;lt;code&amp;gt;class=&amp;quot;inline&amp;quot;&amp;lt;/code&amp;gt; at the same time to use &amp;lt;code&amp;gt;display: inline-block&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;fullOnClick&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether it should open a new page with the full contents on click. Only if &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; is set and the content has been collapsed. &lt;br /&gt;
* &amp;lt;code&amp;gt;fullTitle&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;Description&amp;quot;&amp;lt;/code&amp;gt;. Title to use in full view.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-format-text text=&amp;quot;&amp;lt;% cm.description %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle a link. It performs several checks, like checking if the link needs to be opened in the app, and opens the link as it should (without overriding the app).&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;capture&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether the link needs to be captured by the app (check if the link can be handled by the app instead of opening it in a browser).&lt;br /&gt;
* &amp;lt;code&amp;gt;inApp&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to open in an embedded browser within the app or in the system browser.&lt;br /&gt;
* &amp;lt;code&amp;gt;autoLogin&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt;. If the link should be open with auto-login. Accepts the following values:&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;yes&amp;quot;&amp;lt;/code&amp;gt; — Always auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;no&amp;quot;&amp;lt;/code&amp;gt; — Never auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt; — Auto-login only if it points to the current site.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a href=&amp;quot;&amp;lt;% cm.url %&amp;gt;&amp;quot; core-link&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle links to files and embedded files. This directive should be used in any link to a file or any embedded file that you want to have available when the app is offline. &lt;br /&gt;
&lt;br /&gt;
If a file is downloaded, its URL will be replaced by the local file URL.&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;&amp;lt;% event.iconurl %&amp;gt;&amp;quot; core-external-content component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% event.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-user-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to go to user profile on click. When the user clicks the element where this directive is attached, the right user profile will be opened.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;userId&amp;lt;/code&amp;gt; (number) — User id to open the profile.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — Optional. Course id to show the user info related to that course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a ion-item core-user-link userId=&amp;quot;&amp;lt;% userid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to handle a remote file. It shows the file name, icon (depending on mime type) and a button to download or refresh it. The user can identify if the file is downloaded or not based on the button.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;file&amp;lt;/code&amp;gt; (object) — The file. Must have a &amp;lt;code&amp;gt;filename&amp;lt;/code&amp;gt; property and either &amp;lt;code&amp;gt;fileurl&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component the file belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDelete&amp;lt;/code&amp;gt; (boolean) — Optional. Whether the file can be deleted.&lt;br /&gt;
* &amp;lt;code&amp;gt;alwaysDownload&amp;lt;/code&amp;gt; (boolean) — Optional. Whether it should always display the refresh button when the file is downloaded. Use it for files that you cannot determine if they&#039;re outdated or not.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDownload&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether file can be downloaded. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-file&lt;br /&gt;
        [file]=&amp;quot;{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.url %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.name %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            filesize: &#039;&amp;lt;% issue.size %&amp;gt;&#039;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/core-file&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening a file. When the item with this directive is clicked, the file will be downloaded (if needed) and opened.&lt;br /&gt;
&lt;br /&gt;
It is usually recommended to use the &amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt; component since it also displays the state of the file.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt; (object) — The file to download.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. Component ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage (a button to download a file):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button&lt;br /&gt;
        [core-download-file]=&amp;quot;{&lt;br /&gt;
            fileurl: &amp;lt;% issue.url %&amp;gt;,&lt;br /&gt;
            timemodified: &amp;lt;% issue.timemodified %&amp;gt;,&lt;br /&gt;
            filesize: &amp;lt;% issue.size %&amp;gt;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.download | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening the main file of a module.&lt;br /&gt;
&lt;br /&gt;
When the item with this directive is clicked, the whole module will be downloaded (if needed) and its main file opened. This is meant for modules like &amp;lt;code&amp;gt;mod_resource&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This directive must receive either a &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; or a &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. If no files are provided, it will use &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; (object) — Optional, required if module is not supplied. The module object.&lt;br /&gt;
* &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt; (number) — Optional, required if module is not supplied. The module ID.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — The course ID the module belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional, defaults to the same value as &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. Component ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; (object[]) — Optional. List of files of the module. If not provided, uses &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot; &lt;br /&gt;
        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        [files]=&amp;quot;[{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
        }]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-navbar-buttons&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to add buttons to the app&#039;s header without having to place them inside the header itself. Using this component in a site plugin will allow adding buttons to the header of the current page.&lt;br /&gt;
&lt;br /&gt;
If this component indicates a position (start/end), the buttons will only be added if the header has some buttons in that position. If no start/end is specified, then the buttons will be added to the first &amp;lt;code&amp;gt;&amp;lt;ion-buttons&amp;gt;&amp;lt;/code&amp;gt; found in the header.&lt;br /&gt;
&lt;br /&gt;
You can use the &amp;lt;code&amp;gt;[hidden]&amp;lt;/code&amp;gt; input to hide all the inner buttons if a certain condition is met.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons end&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button (click)=&amp;quot;action()&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-icon slot=&amp;quot;icon-only&amp;quot; name=&amp;quot;funnel&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can also use this to add options to the context menu, for example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons&amp;gt;&lt;br /&gt;
    &amp;lt;core-context-menu&amp;gt;&lt;br /&gt;
        &amp;lt;core-context-menu-item&lt;br /&gt;
                [priority]=&amp;quot;500&amp;quot; content=&amp;quot;Nice boat&amp;quot; (action)=&amp;quot;boatFunction()&amp;quot;&lt;br /&gt;
                iconAction=&amp;quot;boat&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/core-context-menu-item&amp;gt;&lt;br /&gt;
    &amp;lt;/core-context-menu&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Using &#039;font&#039; icons with &amp;lt;code&amp;gt;ion-icon&amp;lt;/code&amp;gt;====&lt;br /&gt;
Font icons are widely used on the app and Moodle LMS website. In order to support [https://fontawesome.com/v5/search?m=free font awesome icons]. We&#039;ve added a directive that uses prefixes on the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; attribute to use different font icons.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fas-&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;fa-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=solid Font awesome solid] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;far-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=regular Font awesome regular] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fab-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=brands Font awesome brands] library (But only a few are supported and we discourage to use them).&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;moodle-&amp;lt;/code&amp;gt; will use some svg icons [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/moodle imported from Moodle LMS].&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fam-&amp;lt;/code&amp;gt; will use [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/font-awesome customized font awesome icons].&lt;br /&gt;
* If the prefix is not found or not valid, the app will search the icon name on the [https://ionic.io/ionicons ionicons library].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example of usage to show icon &amp;quot;pizza-slice&amp;quot; from font-awesome regular library:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-icon name=&amp;quot;fas-pizza-slice&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We encourage the use of font-awesome icons to match the appearance from the LMS website version.&lt;br /&gt;
===Specific component and directives for plugins===&lt;br /&gt;
These are component and directives created specifically for supporting Moodle plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to display a new content when clicked. This new content can be displayed in a new page or in the current page (only if the current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The params to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in v3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in same page or open a new one. &lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that doing &amp;lt;code&amp;gt;[useOtherData]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to go to a new content page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content &lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; &lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to load new content in current page using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot; samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked. The action to do when the WS call is successful depends on the provided data: display a message, go back or refresh current view.&lt;br /&gt;
&lt;br /&gt;
If you want to load a new content when the WS call is done, please see [[#core-site-plugins-call-ws-new-content|&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The params for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;successMessage&amp;lt;/code&amp;gt; (string) — Message to show on success. If not supplied, no message. If supplied but empty, defaults to &amp;quot;Success&amp;quot;.&lt;br /&gt;
* &amp;lt;code&amp;gt;goBackOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to go back if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to refresh the current view if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to send some data to the server without using cache, displaying default messages and refreshing on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage successMessage&lt;br /&gt;
        refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to send some data to the server using cache without confirming, going back on success and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        goBackOnSuccess=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Same as the previous example, but implementing custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;certificateViewed($event)&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.certificateViewed = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked and load a new content passing the WS result as arguments. This new content can be displayed in a new page or in the same page (only if current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to load some new content when done, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The parameters to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in the same page or open a new one.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. The format is the same as in &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;jsData&amp;lt;/code&amp;gt; (any) — JS variables to pass to the new page so they can be used in the template or JS. If &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; is supplied instead of an object, all initial variables from current page will be copied. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;newContentPreSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in 3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to get some data from the server without using cache, showing default confirm and displaying a new page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage&lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to get some data from the server using cache, without confirm, displaying new content in same page and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Same as the previous example, but implementing a custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS as soon as the template is loaded. This directive is meant for actions to do in the background, like calling logging Web Services.&lt;br /&gt;
&lt;br /&gt;
If you want to call a WS when the user clicks on a certain element, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span core-site-plugins-call-ws-on-load&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Advanced features==&lt;br /&gt;
===Display the plugin only if certain conditions are met===&lt;br /&gt;
You might want to display your plugin in the mobile app only if certain dynamic conditions are met, so the plugin would be displayed only for some users. This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section ahead).&lt;br /&gt;
&lt;br /&gt;
All initialisation methods are called as soon as your plugin is retrieved. If you don&#039;t want your plugin to be displayed for the current user, then you should return the following in the initialisation method (only for Moodle site 3.8 and onwards):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;disabled&#039; =&amp;gt; true];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If the Moodle site is older than 3.8, then the initialisation method should return this instead:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;javascript&#039; =&amp;gt; &#039;this.HANDLER_DISABLED&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
On the other hand, you might want to display a plugin only for certain courses (&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;) or only if the user is viewing certain users&#039; profiles (&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;). This can be achieved with the initialisation method too.&lt;br /&gt;
&lt;br /&gt;
In the initialisation method you can return a &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; property with two fields in it: &amp;lt;code&amp;gt;courses&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;users&amp;lt;/code&amp;gt;. If you return a list of courses IDs in this property, then your plugin will only be displayed when the user views any of those courses. In the same way, if you return a list of user IDs then your plugin will only be displayed when the user views any of those users&#039; profiles.&lt;br /&gt;
===Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;===&lt;br /&gt;
The values returned by the functions in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; are added to a variable so they can be used both in JavaScript and in templates. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call is added to a variable named &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt;, while the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call is added to a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call will be passed to the JS and template of all the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; calls in that handler. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call will only be passed to the JS and template returned by that &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call.&lt;br /&gt;
&lt;br /&gt;
This means that, in your JavaScript, you can access and use the data like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.CONTENT_OTHERDATA.myVar;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the template you could use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
{{ CONTENT_OTHERDATA.myVar }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; is the name we put to one of our variables, it can be any name that you want. In the example above, this is the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
[&#039;myVar&#039; =&amp;gt; &#039;Initial value&#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Example====&lt;br /&gt;
In our plugin, we want to display an input text with a certain initial value. When the user clicks a button, we want the value in the input to be sent to a certain Web Service. This can be done using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We will return the initial value of the input in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; of our PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;myVar&#039; =&amp;gt; &#039;My initial value&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then in the template we will use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item text-wrap&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label position=&amp;quot;stacked&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.textlabel | translate }}&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;text&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.myVar&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label&amp;gt;&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws name=&amp;quot;mod_certificate_my_webservice&amp;quot; [useOtherDataForWS]=&amp;quot;[&#039;myVar&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we are creating an input text and we use &amp;lt;code&amp;gt;[(ngModel)]&amp;lt;/code&amp;gt; to use the value in &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; as the initial value and to store the changes in the same &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This means that the initial value of the input will be &amp;quot;My initial value&amp;quot;, and if the user changes the value of the input these changes will be applied to the &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This is called 2-way data binding in Angular.&lt;br /&gt;
&lt;br /&gt;
Then we add a button to send this data to a WS, and for that we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive. We use the &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; attribute to specify which variable from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; we want to send to our WebService. So if the user enters &amp;quot;A new value&amp;quot; in the input and then clicks the button, it will call the WebService &amp;lt;code&amp;gt;mod_certificate_my_webservice&amp;lt;/code&amp;gt; and will send as a parameter &amp;lt;code&amp;gt;[&#039;myVar&#039; =&amp;gt; &#039;A new value&#039;]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We can also achieve the same result using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; attribute of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive instead of using &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws &lt;br /&gt;
        name=&amp;quot;mod_certificate_my_webservice&amp;quot; [params]=&amp;quot;{myVar: CONTENT_OTHERDATA.myVar}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The Web Service call will be exactly the same with both versions.&lt;br /&gt;
&lt;br /&gt;
Notice that this example could be done without using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; too, using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; input of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive.&lt;br /&gt;
===Running JS code after a content template has loaded===&lt;br /&gt;
When you return JavaScript code from a handler function using the &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; array key, this code is executed immediately after the web service call returns, which may be before the returned template has been rendered into the DOM. &lt;br /&gt;
&lt;br /&gt;
If your code needs to run after the DOM has been updated, you can use &amp;lt;code&amp;gt;setTimeout&amp;lt;/code&amp;gt; to call it. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&lt;br /&gt;
    &#039;template&#039; =&amp;gt; [&lt;br /&gt;
        // ...&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;javascript&#039; =&amp;gt; &#039;setTimeout(function() { console.log(&amp;quot;DOM is available now&amp;quot;); });&#039;,&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    &#039;files&#039; =&amp;gt; [],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice that if you wanted to write a lot of code here, you might be better off putting it in a function defined in the response from an initialisation template, so that it does not get loaded again with each page of content.&lt;br /&gt;
===JS functions visible in the templates===&lt;br /&gt;
The app provides some JavaScript functions that can be used from the templates to update, refresh or view content. These are the functions:&lt;br /&gt;
* &amp;lt;code&amp;gt;openContent(title: string, args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Open a new page to display some new content. You need to specify the &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; of the new page and the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshContent(showSpinner = true)&amp;lt;/code&amp;gt; — Refresh the current content. By default, it will display a spinner while refreshing. If you don&#039;t want it to be displayed, you should pass &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; as a parameter.&lt;br /&gt;
* &amp;lt;code&amp;gt;updateContent(args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Refresh the current content using different parameters. You need to specify the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Group selector=====&lt;br /&gt;
Imagine we have an activity that uses groups and we want to let the user select which group they want to see. A possible solution would be to return all the groups in the same template (hidden), and then show the group user selects. However, we can make it more dynamic and return only the group the user is requesting.&lt;br /&gt;
&lt;br /&gt;
To do so, we&#039;ll use a drop down to select the group. When the user selects a group using this drop down, we&#039;ll update the page content to display the new group.&lt;br /&gt;
&lt;br /&gt;
The main difficulty in this is to tell the view which group needs to be selected when the view is loaded. There are 2 ways to do it: using plain HTML or using Angular&#039;s &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;.&lt;br /&gt;
======Using plain HTML======&lt;br /&gt;
We need to add a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; attribute to the option that needs to be selected. To do so, we need to pre-caclulate the selected option in the PHP code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// Detect which group is selected.&lt;br /&gt;
foreach ($groups as $gid=&amp;gt;$group) {&lt;br /&gt;
    $group-&amp;gt;selected = $gid === $groupid;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$data = [&lt;br /&gt;
    &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
    &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
    &#039;groups&#039; =&amp;gt; $groups,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the code above, we&#039;re retrieving the groups the user can see and then we&#039;re adding a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; boolean to each one to determine which one needs to be selected in the drop down. Finally, we pass the list of groups to the template.&lt;br /&gt;
&lt;br /&gt;
In the template, we display the drop down like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: $event})&amp;quot; interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot; &amp;lt;%#selected%&amp;gt;selected&amp;lt;%/selected%&amp;gt; &amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The &amp;lt;code&amp;gt;ionChange&amp;lt;/code&amp;gt; function will be called every time the user selects a different group with the drop down. We&#039;re using the &amp;lt;code&amp;gt;updateContent&amp;lt;/code&amp;gt; function to update the current view using the new group. &amp;lt;code&amp;gt;$event&amp;lt;/code&amp;gt; is an Angular variable that will have the selected value (in our case, the group ID that was just selected). This is enough to make the group selector work.&lt;br /&gt;
======Using &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;======&lt;br /&gt;
&amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; is an Angular directive that allows storing the value of a certain input or select in a JavaScript variable, and also the opposite way: tell the input or select which value to set. The main problem is that we cannot initialise a JavaScript variable from the template, so we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the PHP function we&#039;ll return the group that needs to be selected in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
        &#039;group&#039; =&amp;gt; $groupid,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we don&#039;t need to iterate over the groups array like in the plain HTML example. However, now we&#039;re returning the group id in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array. As it&#039;s explained in the [[#Using_otherdata|Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;]] section, this &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; is visible in the templates inside a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. So in the template we&#039;ll use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.group&amp;quot;&lt;br /&gt;
        (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: CONTENT_OTHERDATA.group})&amp;quot;&lt;br /&gt;
        interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Use the rich text editor===&lt;br /&gt;
The rich text editor included in the app requires a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt; to work. You can use the &amp;lt;code&amp;gt;FormBuilder&amp;lt;/code&amp;gt; library to create this control (or to create a whole &amp;lt;cide&amp;gt;FormGroup if you prefer).&lt;br /&gt;
&lt;br /&gt;
With the following JavaScript you&#039;ll be able to create a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.control = this.FormBuilder.control(this.CONTENT_OTHERDATA.rte);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we&#039;re using a value returned in &amp;lt;code&amp;gt;OTHERDATA&amp;lt;/code&amp;gt; as the initial value of the rich text editor, but you can use whatever you want.&lt;br /&gt;
&lt;br /&gt;
Then you need to pass this control to the rich text editor in your template:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;core-rich-text-editor item-content [control]=&amp;quot;control&amp;quot; placeholder=&amp;quot;Enter your text here&amp;quot; name=&amp;quot;rte_answer&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-rich-text-editor&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, there are several ways to send the value in the rich text editor to a Web Service to save it. This is one of the simplest options:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_webservice&amp;quot; [params]=&amp;quot;{rte: control.value}&amp;quot; ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re passing the value of the rich text editor as a parameter to our Web Service.&lt;br /&gt;
===Initialisation===&lt;br /&gt;
All handlers can specify an &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; method in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. This method is meant to return some JavaScript code that needs to be executed as soon as the plugin is retrieved.&lt;br /&gt;
&lt;br /&gt;
When the app retrieves all the handlers, the first thing it will do is call the &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt; Web Service with the initialisation method. This WS call will only receive the default arguments.&lt;br /&gt;
&lt;br /&gt;
The app will immediately execute the JavaScript code returned by this WS call. This JavaScript can be used to manually register your handlers in the delegates you want, without having to rely on the default handlers built based on the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; data.&lt;br /&gt;
&lt;br /&gt;
The templates returned by this method will be added to a &amp;lt;code&amp;gt;INIT_TEMPLATES&amp;lt;/code&amp;gt; variable that will be passed to all the JavaScript code of that handler. This means that the JavaScript returned by the initialisation method or the main method can access any of the templates HTML like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.INIT_TEMPLATES[&#039;main&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In this case, &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is the ID of the template we want to use.&lt;br /&gt;
&lt;br /&gt;
The same happens with the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the initialisation method, it is added to an &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt; variable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; field returned by this call will be used to determine if your handler is enabled or not. For example, if your handler is for the delegate &amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt; and you return a list of course ids in &amp;lt;code&amp;gt;restrict.courses&amp;lt;/code&amp;gt;, then your handler will only be enabled in the courses you returned. This only applies to the default handlers, if you register your own handler using the JavaScript code then you should check yourself if the handler is enabled.&lt;br /&gt;
&lt;br /&gt;
Finally, if you return an object in this initialisation JavaScript code, all the properties of that object will be passed to all the JavaScript code of that handler so you can use them when the code is run. For example, if your JavaScript code does something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    MyAddonClass: new MyAddonClass()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then, for the rest of JavaScript code of your handler (for example, the main method) you can use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.MyAddonClass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Link handlers=====&lt;br /&gt;
A link handler allows you to decide what to do when a link with a certain URL is clicked. This is useful, for example, to open your plugin page when a link to your plugin is clicked.&lt;br /&gt;
&lt;br /&gt;
After the 4.0 version, the Moodle app automatically creates two link handlers for module plugins, you don&#039;t need to create them in your plugin&#039;s Javascript code anymore:&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginanme/view.php?id=X&#039;&#039;. When this link is clicked, it will open your module in the app.&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginname/index.php?id=X&#039;&#039;. When this link is clicked, it will open a page in the app listing all the modules of your type inside a certain course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Link handlers have some advanced features that allow you to change how links behave under different conditions.&lt;br /&gt;
======Patterns======&lt;br /&gt;
You can define a Regular Expression pattern to match certain links. This will apply the handler only to links that match the pattern.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = RegExp(&#039;\/mod\/foo\/specialpage.php&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Priority======&lt;br /&gt;
Multiple link handlers may apply to a given link. You can define the order of precedence by setting the priority; the handler with the highest priority will be used.&lt;br /&gt;
&lt;br /&gt;
All default handlers have a priority of 0, so 1 or higher will override the default.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Multiple actions======&lt;br /&gt;
Once a link has been matched, the handler&#039;s &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method determines what the link should do. This method has access to the URL and its parameters.&lt;br /&gt;
&lt;br /&gt;
Different actions can be returned depending on different conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                action: function(siteId, navCtrl) {&lt;br /&gt;
                    // The actual behaviour of the link goes here.&lt;br /&gt;
                },&lt;br /&gt;
                sites: [&lt;br /&gt;
                    // ...&lt;br /&gt;
                ],&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                // ...&lt;br /&gt;
            },&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once handlers have been matched for a link, the actions will be fetched for all the matching handlers, in priorty order. The first valid action will be used to open the link.&lt;br /&gt;
&lt;br /&gt;
If your handler is matched with a link, but a condition assessed in the &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method means you want to revert to the next highest priorty handler, you can invalidate your action by settings its sites propety to an empty array.&lt;br /&gt;
======Complex example======&lt;br /&gt;
This will match all URLs containing &amp;lt;code&amp;gt;/mod/foo/&amp;lt;/code&amp;gt;, and force those with an id parameter that&#039;s not in the &amp;lt;code&amp;gt;supportedModFoos&amp;lt;/code&amp;gt; array to open in the user&#039;s browser, rather than the app.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const that = this;&lt;br /&gt;
const supportedModFoos = [...];&lt;br /&gt;
&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = new RegExp(&#039;\/mod\/foo\/&#039;);&lt;br /&gt;
        this.name = &#039;AddonModFooLinkHandler&#039;;&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {     &lt;br /&gt;
        const action = {&lt;br /&gt;
            action() {&lt;br /&gt;
                that.CoreUtilsProvider.openInBrowser(url);&lt;br /&gt;
            },&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (supportedModFoos.indexOf(parseInt(params.id)) !== -1) {&lt;br /&gt;
            action.sites = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return [action];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreContentLinksDelegate.registerHandler(new AddonModFooLinkHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Module prefetch handler=====&lt;br /&gt;
The &amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt; handler allows you to define a list of offline functions to prefetch a module. However, you might want to create your own prefetch handler to determine what needs to be downloaded. For example, you might need to chain WS calls (pass the result of a WS call to the next one), and this cannot be done using offline functions.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to create a prefetch handler using the initialisation JS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Create a class that extends from CoreCourseActivityPrefetchHandlerBase.&lt;br /&gt;
class AddonModCertificateModulePrefetchHandler extends CoreCourseActivityPrefetchHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.name = &#039;AddonModCertificateModulePrefetchHandler&#039;;&lt;br /&gt;
        this.modName = &#039;certificate&#039;;&lt;br /&gt;
&lt;br /&gt;
        // This must match the plugin identifier from db/mobile.php,&lt;br /&gt;
        // otherwise the download link in the context menu will not update correctly.&lt;br /&gt;
        this.component = &#039;mod_certificate&#039;;&lt;br /&gt;
        this.updatesNames = /^configuration$|^.*files$/;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Override the prefetch call.&lt;br /&gt;
    prefetch(module, courseId, single, dirPath) {&lt;br /&gt;
        return this.prefetchPackage(module, courseId, single, prefetchCertificate);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prefetchCertificate(module, courseId, single, siteId) {&lt;br /&gt;
    // Perform all the WS calls.&lt;br /&gt;
    // You can access most of the app providers using that.ClassName. E.g. that.CoreWSProvider.call().&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseModulePrefetchDelegate.registerHandler(new AddonModCertificateModulePrefetchHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
One relatively simple full example is where you have a function that needs to work offline, but it has an additional argument other than the standard ones. You can imagine for this an activity like the book module, where it has multiple pages for the same &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;. The app will not automatically work with this situation — it will call the offline function with the standard arguments only — so you won&#039;t be able to prefetch all the possible parameters.&lt;br /&gt;
&lt;br /&gt;
To deal with this, you need to implement a web service in your Moodle component that returns the list of possible extra arguments, and then you can call this web service and loop around doing the same thing the app does when it prefetches the offline functions. Here is an example from a third-party module (showing only the actual prefetch function, the rest of the code is as above) where there are multiple values of a custom &amp;lt;code&amp;gt;section&amp;lt;/code&amp;gt; parameter for the mobile function &amp;lt;code&amp;gt;mobile_document_view&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function prefetchOucontent(module, courseId, single, siteId) {&lt;br /&gt;
    var component = &#039;mod_oucontent&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Get the site, first.&lt;br /&gt;
    return that.CoreSitesProvider.getSite(siteId).then(function(site) {&lt;br /&gt;
        // Read the list of pages in this document using a web service.&lt;br /&gt;
        return site.read(&#039;mod_oucontent_get_page_list&#039;, {&#039;cmid&#039;: module.id}).then(function(response) {&lt;br /&gt;
            var promises = [];&lt;br /&gt;
&lt;br /&gt;
            // For each page, read and process the page - this is a copy of logic in the app at&lt;br /&gt;
            // siteplugins.ts (prefetchFunctions), but modified to add the custom argument.&lt;br /&gt;
            for(var i = 0; i &amp;lt; response.length; i++) {&lt;br /&gt;
                var args = {&lt;br /&gt;
                    courseid: courseId,&lt;br /&gt;
                    cmid: module.id,&lt;br /&gt;
                    userid: site.getUserId()&lt;br /&gt;
                };&lt;br /&gt;
                if (response[i] !== &#039;&#039;) {&lt;br /&gt;
                    args.section = response[i];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                promises.push(that.CoreSitePluginsProvider.getContent(&lt;br /&gt;
                        component, &#039;mobile_document_view&#039;, args).then(&lt;br /&gt;
                        function(result) {&lt;br /&gt;
                            var subPromises = [];&lt;br /&gt;
                            if (result.files &amp;amp;&amp;amp; result.files.length) {&lt;br /&gt;
                                subPromises.push(that.CoreFilepoolProvider.downloadOrPrefetchFiles(&lt;br /&gt;
                                        site.id, result.files, true, false, component, module.id));&lt;br /&gt;
                            }&lt;br /&gt;
                            return Promise.all(subPromises);&lt;br /&gt;
                        }));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(promises);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Single activity course format=====&lt;br /&gt;
In the following example, the value of &amp;lt;code&amp;gt;INIT_TEMPLATES[&#039;main&#039;]&amp;lt;/code&amp;gt; is:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;componentClass&amp;quot; [data]=&amp;quot;data&amp;quot;&amp;gt;&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This template is returned by the initialisation method. And this is the JavaScript code returned:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatComponent {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.data = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ngOnChanges(changes) {&lt;br /&gt;
        var self = this;&lt;br /&gt;
&lt;br /&gt;
        if (this.course &amp;amp;&amp;amp; this.sections &amp;amp;&amp;amp; this.sections.length) {&lt;br /&gt;
            var module = this.sections[0] &amp;amp;&amp;amp; this.sections[0].modules &amp;amp;&amp;amp; this.sections[0].modules[0];&lt;br /&gt;
            if (module &amp;amp;&amp;amp; !this.componentClass) {&lt;br /&gt;
                that.CoreCourseModuleDelegate.getMainComponent(that.Injector, this.course, module).then((component) =&amp;gt; {&lt;br /&gt;
                    self.componentClass = component || that.CoreCourseUnsupportedModuleComponent;&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.data.courseId = this.course.id;&lt;br /&gt;
            this.data.module = module;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    doRefresh(refresher, done) {&lt;br /&gt;
        return Promise.resolve(this.dynamicComponent.callComponentFunction(&amp;quot;doRefresh&amp;quot;, [refresher, done]));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatHandler {&lt;br /&gt;
    &lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.name = &#039;singleactivity&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    isEnabled() {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canViewAllSections() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseTitle(course, sections) {&lt;br /&gt;
        if (sections &amp;amp;&amp;amp; sections[0] &amp;amp;&amp;amp; sections[0].modules &amp;amp;&amp;amp; sections[0].modules[0]) {&lt;br /&gt;
            return sections[0].modules[0].name;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return course.fullname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displayEnableDownload() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displaySectionSelector() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseFormatComponent() {&lt;br /&gt;
        return that.CoreCompileProvider.instantiateDynamicComponent(that.INIT_TEMPLATES[&#039;main&#039;], AddonSingleActivityFormatComponent);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseFormatDelegate.registerHandler(new AddonSingleActivityFormatHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using the JavaScript API===&lt;br /&gt;
The JavaScript API is only supported by the delegates specified in the [[#Templates_downloaded_on_login_and_rendered_using_JS_data_2|Templates downloaded on login and rendered using JS data]] section. This API allows you to override any of the functions of the default handler.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; specified in a handler registered in the &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; will be called immediately after the initialisation method, and the JavaScript returned by this method will be run. If this JavaScript code returns an object with certain functions, these functions will override the ones in the default handler.&lt;br /&gt;
&lt;br /&gt;
For example, if the JavaScript returned by the method returns something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The the &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function of the default handler will be overridden by the returned &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
The default handler for &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; only has 2 functions: &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt;. In addition, the JavaScript code can return an extra function named &amp;lt;code&amp;gt;componentInit&amp;lt;/code&amp;gt; that will be executed when the component returned by &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; is initialised.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to support the text user profile field using this API:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
var result = {&lt;br /&gt;
    componentInit: function() {&lt;br /&gt;
        if (this.field &amp;amp;&amp;amp; this.edit &amp;amp;&amp;amp; this.form) {&lt;br /&gt;
            this.field.modelName = &#039;profile_field_&#039; + this.field.shortname;&lt;br /&gt;
&lt;br /&gt;
            if (this.field.param2) {&lt;br /&gt;
                this.field.maxlength = parseInt(this.field.param2, 10) || &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.field.inputType = that.CoreUtilsProvider.isTrueOrOne(this.field.param3) ? &#039;password&#039; : &#039;text&#039;;&lt;br /&gt;
&lt;br /&gt;
            var formData = {&lt;br /&gt;
                value: this.field.defaultdata,&lt;br /&gt;
                disabled: this.disabled,&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            this.form.addControl(this.field.modelName,&lt;br /&gt;
                that.FormBuilder.control(formData, this.field.required &amp;amp;&amp;amp; !this.field.locked ? that.Validators.required : null));&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        var name = &#039;profile_field_&#039; + field.shortname;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            type: &amp;quot;text&amp;quot;,&lt;br /&gt;
            name: name,&lt;br /&gt;
            value: that.CoreTextUtilsProvider.cleanTags(formValues[name]),&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Translate dynamic strings===&lt;br /&gt;
If you wish to have an element that displays a localised string based on value from your template you can doing something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_myactivity.&amp;lt;% status %&amp;gt;&#039; | translate }}&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This could save you from having to write something like when only one value should be displayed:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        &amp;lt;%#isedting%&amp;gt;{{ &#039;plugin.mod_myactivity.editing&#039; | translate }}&amp;lt;%/isediting%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isopen%&amp;gt;{{ &#039;plugin.mod_myactivity.open&#039; | translate }}&amp;lt;%/isopen%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isclosed%&amp;gt;{{ &#039;plugin.mod_myactivity.closed&#039; | translate }}&amp;lt;%/isclosed%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using strings with dates===&lt;br /&gt;
If you have a string that you wish to pass a formatted date, for example in the Moodle language file you have:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;strwithdate&#039;] = &#039;This string includes a date of {$a-&amp;gt;date} in the middle of it.&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can localise the string correctly in your template using something like the following:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{ &#039;plugin.mod_myactivity.strwithdate&#039; | translate: {$a: { date: &amp;lt;% timestamp %&amp;gt; * 1000 | coreFormatDate: &amp;quot;dffulldate&amp;quot; } } }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A Unix timestamp must be multiplied by 1000 as the Mobile App expects millisecond timestamps, whereas Unix timestamps are in seconds.&lt;br /&gt;
===Support push notification clicks===&lt;br /&gt;
If your plugin sends push notifications to the app, you might want to open a certain page in the app when the notification is clicked. There are several ways to achieve this.&lt;br /&gt;
&lt;br /&gt;
The easiest way is to include a &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; in your notification. When the notification is clicked, the app will try to open the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; will also be displayed in web. If you want to use a specific URL for the app, different than the one displayed in web, you can do so by returning a &amp;lt;code&amp;gt;customdata&amp;lt;/code&amp;gt; array that contains an &amp;lt;code&amp;gt;appurl&amp;lt;/code&amp;gt; property:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$notification-&amp;gt;customdata = [&lt;br /&gt;
    &#039;appurl&#039; =&amp;gt; $myurl-&amp;gt;out(),&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In both cases you will have to create a link handler to treat the URL. For more info on how to create the link handler, please see [[#Advanced_link_handler|how to create an advanced link handler]].&lt;br /&gt;
&lt;br /&gt;
If you want to do something that only happens when the notification is clicked, not when the link is clicked, you&#039;ll have to implement a push click handler yourself. The way to create it is similar to [[#Advanced_link_handler|creating an advanced link handler]], but you&#039;ll have to use &amp;lt;code&amp;gt;CorePushNotificationsDelegate&amp;lt;/code&amp;gt; and your handler will have to implement the properties and functions defined in the [https://github.com/moodlehq/moodleapp/blob/master/src/core/features/pushnotifications/services/push-delegate.ts#L27 CorePushNotificationsClickHandler] interface.&lt;br /&gt;
===Implement a module similar to mod_label===&lt;br /&gt;
In Moodle 3.8 or higher, if your plugin doesn&#039;t support &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt; and you don&#039;t specify a &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; then the module will only display the module description in the course page and it won&#039;t be clickable in the app, just like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt;. You can decide if you want the module icon to be displayed or not (if you don&#039;t want it to be displayed, then don&#039;t define it in &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
However, if your plugin needs to work in previous versions of Moodle or you want to display something different than the description then you need a different approach.&lt;br /&gt;
&lt;br /&gt;
If your plugin wants to render something in the course page instead of just the module name and description you should specify the &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. The template returned by this method will be rendered in the course page. Please notice the HTML returned should not contain directives or components, only plain HTML.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want your module to be clickable then you just need to remove &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. With these 2 changes you can have a module that behaves like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt; in the app.&lt;br /&gt;
===Use Ionic navigation lifecycle functions===&lt;br /&gt;
Ionic let pages define some functions that will be called when certain navigation lifecycle events happen. For more info about these functions, see [https://ionicframework.com/docs/api/router-outlet Ionic&#039;s documentation].&lt;br /&gt;
&lt;br /&gt;
You can define these functions in your plugin javascript:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.ionViewWillLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In addition to that, you can also implement &amp;lt;code&amp;gt;canLeave&amp;lt;/code&amp;gt; to use Angular route guards:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.canLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So for example you can make your plugin ask for confirmation if the user tries to leave the page when he has some unsaved data.&lt;br /&gt;
===Module plugins: dynamically determine if a feature is supported===&lt;br /&gt;
In Moodle you can specify if your plugin supports a certain feature, like &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. If your plugin will always support or not a certain feature, then you can use the &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; to specify it ([[#Options_only_for_CoreCourseModuleDelegate|see more documentation about this]]). But if you need to calculate it dynamically then you will have to create a function to calculate it. &lt;br /&gt;
&lt;br /&gt;
This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section above). The JavaScript returned by your initialisation method will need to define a function named &amp;lt;code&amp;gt;supportsFeature&amp;lt;/code&amp;gt; that will receive the name of the feature:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    supportsFeature: function(featureName) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;.&lt;br /&gt;
== Testing ==&lt;br /&gt;
You can also write automated tests for your plugin using Behat, you can read more about it on the [[Acceptance testing for the Moodle App]] page.&lt;br /&gt;
== Upgrading plugins from an older version ==&lt;br /&gt;
If you added mobile support to your plugin for the Ionic 3 version of the app (previous to the 3.9.5 release), you will probably need to make some changes to make it compatible with Ionic 5.&lt;br /&gt;
&lt;br /&gt;
Learn more at the [[Moodle App Plugins Upgrade Guide]].&lt;br /&gt;
==Troubleshooting==&lt;br /&gt;
=== Invalid response received ===&lt;br /&gt;
You might receive this error when using the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive or similar. By default, the app expects all Web Service calls to return an object, if your Web Service returns another type (string, boolean, etc.) then you need to specify it using the &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; attribute of the directive. For example, if your WS returns a boolean value, then you should specify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{typeExpected: &#039;boolean&#039;}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In a similar way, if your Web Service returns &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; you need to tell the app not to expect any result using &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{responseExpected: false}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS ===&lt;br /&gt;
Some directives allow you to specify a form id or name to send the data from the form to a certain WS. These directives look for HTML inputs to retrieve the data to send. However, &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; don&#039;t use HTML inputs, they simulate them, so the directive isn&#039;t going to find their data and so it won&#039;t be sent to the Web Service.&lt;br /&gt;
&lt;br /&gt;
There are 2 workarounds to fix this problem.&lt;br /&gt;
==== Sending the data manually ====&lt;br /&gt;
The first solution is to send the missing params manually using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; property. We will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input value in a variable, and this variable will be passed to the parameters. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a template like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group name=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, responses: responses}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Basically, you need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to the affected element (in this case, the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt;). You can put whatever name you want as the value, we used &amp;quot;responses&amp;quot;. With this, every time the user selects a radio button the value will be stored in a variable called &amp;quot;responses&amp;quot;. Then, in the button we are passing this variable to the parameters of the Web Service.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute has priority over &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;, so if you have an input with &amp;lt;code&amp;gt;name=&amp;quot;responses&amp;quot;&amp;lt;/code&amp;gt; it will override what you&#039;re manually passing to &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;.&lt;br /&gt;
==== Using a hidden input ====&lt;br /&gt;
Since the directive is looking for HTML inputs, you need to add one with the value to send to the server. You can use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to synchronise your radio/checkbox/select with the new hidden input. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a radio button like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot; [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;hidden&amp;quot; [ngModel]=&amp;quot;responses&amp;quot; name=&amp;quot;responses&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we&#039;re using a variable called &amp;quot;responses&amp;quot; to synchronise the data between the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt; and the hidden input. You can use whatever name you want.&lt;br /&gt;
=== I can&#039;t return an object or array in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; ===&lt;br /&gt;
If you try to return an object or an array in any field inside &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, the Web Service call will fail with the following error:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Scalar type expected, array or object received&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Each field in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; must be a string, number or boolean; it cannot be an object or array. To make it work, you need to encode your object or array into a JSON string:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($data)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app will automatically parse this JSON and convert it back into an array or object.&lt;br /&gt;
==Examples==&lt;br /&gt;
===Accepting dynamic names in a Web Service===&lt;br /&gt;
We want to display a form where the names of the fields are dynamic, like it happens in quiz. This data will be sent to a new Web Service that we have created.&lt;br /&gt;
&lt;br /&gt;
The first issue we find is that the Web Service needs to define the names of the parameters received, but in this case they&#039;re dynamic. The solution is to accept an array of objects with name and value. So in the &amp;lt;code&amp;gt;_parameters()&amp;lt;/code&amp;gt; function of our new Web Service, we will add this parameter:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;data&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
     new external_single_structure(&lt;br /&gt;
        [&lt;br /&gt;
            &#039;name&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data name&#039;),&lt;br /&gt;
            &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data value&#039;),&lt;br /&gt;
        ]&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;The data to be saved&#039;, VALUE_DEFAULT, []&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now we need to adapt our form to send the data as the Web Service requires it. In our template, we have a button with the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive that will send the form data to our Web Service. To make this work we will have to pass the parameters manually, without using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute, because we need to format the data before it is sent.&lt;br /&gt;
&lt;br /&gt;
Since we will send the parameters manually and we want it all to be sent in the same array, we will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input data into a variable that we&#039;ll call &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;, but you can use the name you want. This variable will be an object that will hold the input data with the format &amp;quot;name-&amp;gt;value&amp;quot;. For example, if I have an input with name &amp;quot;a1&amp;quot; and value &amp;quot;My answer&amp;quot;, the data object will be:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{a1: &#039;My answer&#039;}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So we need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to all the inputs whose values need to be sent to the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; WS param. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-input name=&amp;quot;&amp;lt;% name %&amp;gt;&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.data[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re using &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; to store the data. We do it like this because we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the form, setting the values the user has already stored. If you don&#039;t need to initialise the form, then you can use the &amp;lt;code&amp;gt;dataObject&amp;lt;/code&amp;gt; variable, an empty object that the mobile app creates for you:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[(ngModel)]=&amp;quot;dataObject[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app has a function that allows you to convert this data object into an array like the one the WS expects: &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt;. So in our button we&#039;ll use this function to format the data before it&#039;s sent:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_ws_name&amp;quot;&lt;br /&gt;
    [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, data: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.data, &#039;name&#039;, &#039;value&#039;)}&amp;quot;&lt;br /&gt;
    successMessage&lt;br /&gt;
    refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see in the example above, we&#039;re specifying that the keys of the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object need to be stored in a property called &amp;quot;name&amp;quot;, and the values need to be stored in a property called &amp;quot;value&amp;quot;. If your Web Service expects different names you need to change the parameters of the &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
If you open your plugin now in the app it will display an error in the JavaScript console. The reason is that the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; variable doesn&#039;t exist inside &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. As it is explained in previous sections, &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; holds the data that you return in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; for your method. We&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the values to be displayed in the form.&lt;br /&gt;
&lt;br /&gt;
If the user hasn&#039;t answered the form yet, we can initialise the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object as an empty object. Please remember that we cannot return arrays or objects in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, so we&#039;ll return a JSON string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; &#039;{}&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
With the code above, the form will always be empty when the user opens it. But now we want to check if the user has already answered the form and fill the form with the previous values. We will do it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$userdata = get_user_responses(); // It will held the data in a format name-&amp;gt;value. Example: [&#039;a1&#039; =&amp;gt; &#039;My value&#039;].&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($userdata)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now the user will be able to see previous values when the form is opened, and clicking the button will send the data to our Web Service in array format.&lt;br /&gt;
==Moodle plugins with mobile support==&lt;br /&gt;
* Group choice: [https://moodle.org/plugins/mod_choicegroup Moodle plugins directory entry] and [https://github.com/ndunand/moodle-mod_choicegroup code in github].&lt;br /&gt;
* Custom certificate: [https://moodle.org/plugins/mod_customcert Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_customcert code in github].&lt;br /&gt;
* Gapfill question type: [https://moodle.org/plugins/qtype_gapfill Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_gapfill in github].&lt;br /&gt;
* Wordselect question type: [https://moodle.org/plugins/qtype_wordselect Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_wordselect in github].&lt;br /&gt;
* RegExp question type: [https://moodle.org/plugins/qtype_regexp Moodle plugins directory entry] and [https://github.com/rezeau/moodle-qtype_regexp in github].&lt;br /&gt;
* Certificate: [https://moodle.org/plugins/mod_certificate Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_certificate in github].&lt;br /&gt;
* Attendance [https://moodle.org/plugins/mod_attendance Moodle plugins directory entry] and [https://github.com/danmarsden/moodle-mod_attendance in github].&lt;br /&gt;
* ForumNG (unfinished support) [https://moodle.org/plugins/mod_forumng Moodle plugins directory entry] and [https://github.com/moodleou/moodle-mod_forumng in github].&lt;br /&gt;
* News block [https://github.com/moodleou/moodle-block_news in github].&lt;br /&gt;
* H5P activity module [https://moodle.org/plugins/mod_hvp Moodle plugins directory entry] and [https://github.com/h5p/h5p-moodle-plugin in github].&lt;br /&gt;
See the complete list in [https://moodle.org/plugins/browse.php?list=award&amp;amp;id=6 the plugins database] (it may contain some outdated plugins).&lt;br /&gt;
=== Mobile app support award ===&lt;br /&gt;
If you want your plugin to be awarded in the plugins directory and marked as supporting the mobile app, please feel encouraged to contact us via email at [mailto:mobile@moodle.com mobile@moodle.com].&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to include a link to your plugin page and the location of its code repository.&lt;br /&gt;
&lt;br /&gt;
See [https://moodle.org/plugins/?q=award:mobile-app the list of awarded plugins] in the plugins directory.&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Moodle App Ionic 5]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62060</id>
		<title>Moodle App Plugins Development Guide</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62060"/>
		<updated>2022-04-25T08:24:11Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: /* Options only for CoreSettingsDelegate */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle App (Ionic 5)}}&lt;br /&gt;
If you want to add mobile support to your Moodle plugin, you can achieve it by extending different areas of the app using &#039;&#039;just PHP server side code&#039;&#039; and providing templates written with [https://ionicframework.com/docs/components Ionic] and custom components.&lt;br /&gt;
&lt;br /&gt;
You will have to:&lt;br /&gt;
# Create a &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file in your plugin. In this file, you will be able to indicate which areas of the app you want to extend. For example, adding a new option in the main menu, implementing support for a new activity module, including a new option in the course menu, including a new option in the user profile, etc. All the areas supported are described further in this document.&lt;br /&gt;
# Create new functions in a reserved namespace that will return the content of the new options. The content should be returned rendered as html. This html should use Ionic components so that it looks native, but it can be generated using mustache templates.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let’s clarify some points:&lt;br /&gt;
* You don’t need to create new Web Service functions (although you will be able to use them for advanced features). You just need plain php functions that will be placed in a reserved namespace.&lt;br /&gt;
* Those functions will be exported via the Web Service function &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt;.&lt;br /&gt;
* As arguments of your functions you will always receive the &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, some relevant details of the app (like the app version or the current language in the app), and some specific data depending on the type of plugin (&amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
* The mobile app also implements a list of custom Ionic components and directives that provide dynamic behaviour; like indicating that you are linking a file that can be downloaded, allowing a transition to new pages into the app calling a specific function in the server, submitting form data to the server, etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Getting started==&lt;br /&gt;
If you only want to write a plugin, it is not necessary that you set up your environment to work with the Moodle App. In fact, you don&#039;t even need to compile it. You can just [[Using the Moodle App in a browser|use a Chromium-based browser]] to add mobile support to your plugins!&lt;br /&gt;
&lt;br /&gt;
You can use the app from one of the hosted versions on [https://master.apps.moodledemo.net master.apps.moodledemo.net] (the latest stable version) and [https://integration.apps.moodledemo.net integration.apps.moodledemo.net] (the latest development version). If you need any specific environment (hosted versions are deployed with a &#039;&#039;production&#039;&#039; environment), you can also use [[Moodle App Docker Images|Docker images]]. And if you need to test your plugin in a native device, you can always use [https://download.moodle.org/mobile Moodle HQ&#039;s application].&lt;br /&gt;
&lt;br /&gt;
This should suffice for developing plugins. However, if you are working on advanced functionality and you need to run the application from the source code, you can find more information in the [[Moodle App Development Guide]].&lt;br /&gt;
===Development workflow===&lt;br /&gt;
Before getting into the specifics of your plugin, we recommend that you start adding a simple &amp;quot;Hello World&amp;quot; button in the app to see that everything works properly.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s say your plugin is called &amp;lt;code&amp;gt;local_hello&amp;lt;/code&amp;gt;, you can start by adding the following files:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;local_hello&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [&lt;br /&gt;
            &#039;hello&#039; =&amp;gt; [&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreMainMenuDelegate&#039;,&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;view_hello&#039;,&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;title&#039; =&amp;gt; &#039;hello&#039;,&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; &#039;earth&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [&lt;br /&gt;
            [&#039;hello&#039;, &#039;local_hello&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace local_hello\output;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    public static function view_hello() {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; &#039;&amp;lt;h1 class=&amp;quot;text-center&amp;quot;&amp;gt;{{ &amp;quot;plugin.local_hello.hello&amp;quot; | translate }}&amp;lt;/h1&amp;gt;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;lang/en/local_hello.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;hello&#039;] = &#039;Hello World&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once you&#039;ve done that, try logging into your site in the app and you should see a new button in the main menu or more menu (depending on the device) saying &amp;quot;Hello World&amp;quot;. If you press this button, you should see a page saying &amp;quot;Hello World!&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Congratualtions, you have written your first Moodle plugin with moodle support!&lt;br /&gt;
&lt;br /&gt;
You can read the rest of this page to learn more about mobile plugins and start working on your plugin. Here&#039;s some things to keep in mind:&lt;br /&gt;
* If you change the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file, you will have to refresh the browser. And remember to [https://developer.chrome.com/docs/devtools/network/reference/#disable-cache disable the network cache].&lt;br /&gt;
* If you change an existing template or function, you won’t have to refresh the browser. In most cases, doing a PTR (Pull To Refresh) in the page that displays the template will suffice.&lt;br /&gt;
* If any of these doesn&#039;t show your changes, you may need to [https://docs.moodle.org/311/en/Developer_tools#Purge_all_caches purge all caches] to avoid problems with the auto-loading cache.&lt;br /&gt;
* Ultimately, if that doesn&#039;t work either, you may have to log out from the site and log in again. If any changes affect plugin installation, you may also need to increase the version in your plugin&#039;s &amp;lt;code&amp;gt;version.php&amp;lt;/code&amp;gt; file and upgrade it in the site.&lt;br /&gt;
==Types of plugins==&lt;br /&gt;
There are 3 types of plugins:&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
[[File:Templates_downloaded_when_requested.png|thumb]]&lt;br /&gt;
With this type of plugin, the template of your plugin will be generated and downloaded when the user opens the plugin in the app. This means that your function will receive some context parameters. For example, if you&#039;re developing a course module plugin you will receive the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; (course module ID). You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
[[File:Templates_downloaded_on_login.png|thumb]]&lt;br /&gt;
With this type of plugin, the template for your plugin will be downloaded when the user logs in into the app and will be stored in the device. This means that your function will not receive any context parameters, and you need to return a generic template that will be built with JS data like the ones in the Moodle App. When the user opens a page that includes your plugin, your template will receive the required JS data and your template will be rendered. You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
You can always implement the whole plugin yourself using JavaScript instead of using our API. In fact, this is required if you want to implement some features like capturing links in the Mobile app. You can see the list of delegates that only support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
==Step by step example==&lt;br /&gt;
In this example, we are going to update an existing plugin, the [https://github.com/mdjnelson/moodle-mod_certificate Certificate activity module], that previously used a [[Moodle Mobile 2 (Ionic 1) Remote add-ons|Remote add-on]] (a legacy approach to implement mobile plugins).&lt;br /&gt;
&lt;br /&gt;
This is a simple activity module that displays the certificate issued for the current user along with the list of the dates of previously issued certificates. It also stores in the course log that the user viewed a certificate. This module also works offline: when the user downloads the course or activity, the data is pre-fetched and can be viewed offline.&lt;br /&gt;
===Step 1. Update the &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file===&lt;br /&gt;
In this case, we are updating an existing file. For new plugins, you should create this new file.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;mod_certificate&#039; =&amp;gt; [ // Plugin identifier&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;coursecertificate&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/mod/certificate/pix/icon.gif&#039;,&lt;br /&gt;
                    &#039;class&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
       &lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseModuleDelegate&#039;, // Delegate (where to display the link to the plugin)&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;mobile_course_view&#039;, // Main function in \mod_certificate\output\mobile&lt;br /&gt;
                &#039;offlinefunctions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;mobile_course_view&#039; =&amp;gt; [],&lt;br /&gt;
                    &#039;mobile_issues_view&#039; =&amp;gt; [],&lt;br /&gt;
                ], // Function that needs to be downloaded for offline.&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [ // Language strings that are used in all the handlers.&lt;br /&gt;
            [&#039;pluginname&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;summaryofattempts&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;getcertificate&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;requiredtimenotmet&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;viewcertificateviews&#039;, &#039;certificate&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Plugin identifier:&lt;br /&gt;
: A unique name for the plugin, it can be anything (there’s no need to match the module name).&lt;br /&gt;
&lt;br /&gt;
;Handlers (Different places where the plugin will display content):&lt;br /&gt;
: A plugin can be displayed in different views in the app. Each view should have a unique name inside the plugin scope (alphanumeric).&lt;br /&gt;
&lt;br /&gt;
; Display data:&lt;br /&gt;
: This is only needed for certain types of plugins. Also, depending on the type of delegate it may require additional (or less fields). In this case, we are indicating the module icon.&lt;br /&gt;
&lt;br /&gt;
; Delegate&lt;br /&gt;
: Where to display the link to the plugin, see the [[#Delegates|Delegates]] section for all the possible options.&lt;br /&gt;
&lt;br /&gt;
; Method:&lt;br /&gt;
: This is the method in the Moodle &amp;lt;code&amp;gt;\{component-name}\output\mobile&amp;lt;/code&amp;gt; class to be executed the first time the user clicks in the new option displayed in the app.&lt;br /&gt;
&lt;br /&gt;
; Offlinefunctions&lt;br /&gt;
: This is the list of functions that need to be called and stored when the user downloads a course for offline usage. Please note that you can add functions here that are not even listed in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. &lt;br /&gt;
: In our example, downloading for offline access will mean that we&#039;ll execute the functions for getting the certificate and issued certificates passing as parameters the current &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; (and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; when we are using the mod or course delegate). If we have the result of those functions stored in the app, we&#039;ll be able to display the certificate information even if the user is offline.&lt;br /&gt;
: Offline functions will be mostly used to display information for final users, any further interaction with the view won’t be supported offline (for example, trying to send information when the user is offline).&lt;br /&gt;
: You can indicate here other Web Services functions, indicating the parameters that they might need from a defined subset (currently &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Prefetching the module will also download all the files returned by the methods in these offline functions (in the &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; array).&lt;br /&gt;
: Note that if your functions use additional custom parameters (for example, if you implement multiple pages within a module&#039;s view function by using a &amp;lt;code&amp;gt;page&amp;lt;/code&amp;gt; parameter in addition to the usual &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;) then the app will not know which additional parameters to supply. In this case, do not list the function in &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt;; instead, you will need to manually implement a [[#Module_prefetch_handler|module prefetch handler]].&lt;br /&gt;
&lt;br /&gt;
;Lang:&lt;br /&gt;
: The language pack string ids used in the plugin by all the handlers. Normally these will be strings from your own plugin, however, you can list any strings you need here, like &amp;lt;code&amp;gt;[&#039;cancel&#039;, &#039;moodle&#039;]&amp;lt;/code&amp;gt;. If you do this, be warned that in the app you will then need to refer to that string as &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.myplugin.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (not &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.moodle.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Please only include the strings you actually need. The Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform, and this will then be cached, so listing too many strings is very wasteful.&lt;br /&gt;
There are additional attributes supported by the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; list, you can find about them in the [[#Mobile.php_supported_options|Mobile.php supported options]] section.&lt;br /&gt;
===Step 2. Creating the main function===&lt;br /&gt;
The main function displays the current issued certificate (or several warnings if it’s not possible to issue a certificate). It also displays a link to view the dates of previously issued certificates.&lt;br /&gt;
&lt;br /&gt;
All the functions must be created in the plugin or subsystem &amp;lt;code&amp;gt;classes/output&amp;lt;/code&amp;gt; directory, the name of the class must be &amp;lt;code&amp;gt;mobile&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
For this example, the namespace name will be &amp;lt;code&amp;gt;mod_certificate\output&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace mod_certificate\output;&lt;br /&gt;
&lt;br /&gt;
use context_module;&lt;br /&gt;
use mod_certificate_external;&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Returns the certificate course view for the mobile app.&lt;br /&gt;
     *&lt;br /&gt;
     * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
     *&lt;br /&gt;
     * @return array       HTML, JS and other data.&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
        $args = (object) $args;&lt;br /&gt;
        $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
        // Capabilities check.&lt;br /&gt;
        require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
        $context = \context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
        require_capability(&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
        if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
            require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
        }&lt;br /&gt;
        $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
        // Get certificates from external (taking care of exceptions).&lt;br /&gt;
        try {&lt;br /&gt;
            $issued = \mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
            $certificates = \mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
            $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $issues = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Set timemodified for each certificate.&lt;br /&gt;
        foreach ($issues as $issue) {&lt;br /&gt;
            if (empty($issue-&amp;gt;timemodified)) {&lt;br /&gt;
                $issue-&amp;gt;timemodified = $issue-&amp;gt;timecreated;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $showget = true;&lt;br /&gt;
        if ($certificate-&amp;gt;requiredtime &amp;amp;&amp;amp; !has_capability(&#039;mod/certificate:manage&#039;, $context)) {&lt;br /&gt;
            if (certificate_get_course_time($certificate-&amp;gt;course) &amp;lt; ($certificate-&amp;gt;requiredtime * 60)) {&lt;br /&gt;
                $showget = false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $certificate-&amp;gt;name = format_string($certificate-&amp;gt;name);&lt;br /&gt;
        [$certificate-&amp;gt;intro, $certificate-&amp;gt;introformat] =&lt;br /&gt;
                external_format_text($certificate-&amp;gt;intro, $certificate-&amp;gt;introformat, $context-&amp;gt;id, &#039;mod_certificate&#039;, &#039;intro&#039;);&lt;br /&gt;
        $data = [&lt;br /&gt;
            &#039;certificate&#039; =&amp;gt; $certificate,&lt;br /&gt;
            &#039;showget&#039; =&amp;gt; $showget &amp;amp;&amp;amp; count($issues) &amp;gt; 0,&lt;br /&gt;
            &#039;issues&#039; =&amp;gt; $issues,&lt;br /&gt;
            &#039;issue&#039; =&amp;gt; $issues[0],&lt;br /&gt;
            &#039;numissues&#039; =&amp;gt; count($issues),&lt;br /&gt;
            &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
            &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;files&#039; =&amp;gt; $issues,&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Function declaration: &lt;br /&gt;
: The function name is the same as the one used in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file (&amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; field). There is only one argument, &amp;lt;code&amp;gt;$args&amp;lt;/code&amp;gt;, which is an array containing all the information sent by the mobile app (the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversionname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversioncode&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;applang&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appcustomurlscheme&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
&lt;br /&gt;
; Function implementation:&lt;br /&gt;
: In the first part of the function, we check permissions and capabilities (like a &amp;lt;code&amp;gt;view.php&amp;lt;/code&amp;gt; script would do normally). Then we retrieve the certificate information that’s necessary to display the template.&lt;br /&gt;
&lt;br /&gt;
; Function return:&lt;br /&gt;
* &amp;lt;code&amp;gt;templates&amp;lt;/code&amp;gt; — The rendered template (notice that we could return more than one template, but we usually would only need one). By default the app will always render the first template received, the rest of the templates can be used if the plugin defines some JavaScript code.&lt;br /&gt;
* &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; — Empty, because we don’t need any in this case.&lt;br /&gt;
* &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; — Empty as well, because we don’t need any additional data to be used by directives or components in the template. This field will be published as an object supporting 2-way data-binding in the template.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; — A list of files that the app should be able to download (for offline usage mostly).&lt;br /&gt;
===Step 3. Creating the template for the main function===&lt;br /&gt;
This is the most important part of your plugin because it contains the code that will be rendered on the mobile app.&lt;br /&gt;
&lt;br /&gt;
In this template we’ll be using Ionic, together with directives and components specific to the Moodle App.&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;ion-&amp;lt;/code&amp;gt; are ionic components. Most of the time, the component name is self-explanatory but you may refer to a detailed guide here: https://ionicframework.com/docs/components/&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;core-&amp;lt;/code&amp;gt; are custom components of the Moodle App.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_page.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;core-course-module-description description=&amp;quot;&amp;lt;% certificate.intro %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-course-module-description&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;ion-list-header&amp;gt;&lt;br /&gt;
            &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.summaryofattempts&#039; | translate }}&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-list-header&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-new-content title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; &lt;br /&gt;
                        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
                        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
                    {{ &#039;plugin.mod_certificate.viewcertificateviews&#039; | translate: {$a: &amp;lt;% numissues %&amp;gt;} }}&lt;br /&gt;
                &amp;lt;/ion-button&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&lt;br /&gt;
                        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
                        [files]=&amp;quot;[{&lt;br /&gt;
                            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
                            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
                            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
                        }]&amp;quot;&amp;gt;&lt;br /&gt;
                 &lt;br /&gt;
                        &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot; slot=&amp;quot;start&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                            {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
                    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%^showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;p&amp;gt;{{ &#039;plugin.mod_certificate.requiredtimenotmet&#039; | translate }}&amp;lt;/p&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;!-- Call log WS when the template is loaded. --&amp;gt;&lt;br /&gt;
        &amp;lt;span core-site-plugins-call-ws-on-load name=&amp;quot;mod_certificate_view_certificate&amp;quot;&lt;br /&gt;
                [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/span&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache).&lt;br /&gt;
&lt;br /&gt;
Then we display the module description using &amp;lt;code&amp;gt;core-course-module-description&amp;lt;/code&amp;gt;, which is a component used to include the course module description.&lt;br /&gt;
&lt;br /&gt;
For displaying the certificate information we create a list of elements, adding a header on top.&lt;br /&gt;
&lt;br /&gt;
The following line using the &amp;lt;code&amp;gt;translate&amp;lt;/code&amp;gt; filter indicates that the app will translate the &amp;lt;code&amp;gt;summaryofattempts&amp;lt;/code&amp;gt; string id (here we could’ve used mustache translation but it is usually better to delegate the strings translations to the app). The string id has the following format: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
plugin.{plugin-identifier}.{string-id}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Where &amp;lt;code&amp;gt;{plugin-identifier}&amp;lt;/code&amp;gt; is taken from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;{string-id}&amp;lt;/code&amp;gt; must be indicated in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; field in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Then, we display a button to transition to another page if there are certificates issued. The attribute (directive) &amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt; indicates that if the user clicks the button, we need to call the &amp;lt;code&amp;gt;mobile_issues_view&amp;lt;/code&amp;gt; function in the &amp;lt;code&amp;gt;mod_certificate&amp;lt;/code&amp;gt; component; passing as arguments the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;. The content returned by this function will be displayed in a new page (read the following section to see the code of this new page).&lt;br /&gt;
&lt;br /&gt;
Just after this button, we display another one but this time for downloading an issued certificate. The &amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt; directive indicates that clicking this button is for downloading the whole activity and opening the main file. This means that, when the user clicks this button, the whole certificate activity will be available offline.&lt;br /&gt;
&lt;br /&gt;
Finally, just before the &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt; is closed, we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt; directive to indicate that once the page is loaded, we need to call a Web Service function in the server, in this case we are calling the &amp;lt;code&amp;gt;mod_certificate_view_certificate&amp;lt;/code&amp;gt; that will log that the user viewed this page.&lt;br /&gt;
&lt;br /&gt;
As you can see, no JavaScript was necessary at all. We used plain HTML elements and attributes that did all the complex dynamic logic (like calling a Web Service) behind the scenes.&lt;br /&gt;
&lt;br /&gt;
===Step 4. Adding an additional page===&lt;br /&gt;
Add the following method to &amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Returns the certificate issues view for the mobile app.&lt;br /&gt;
 * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
 *&lt;br /&gt;
 * @return array       HTML, JS and other data.&lt;br /&gt;
 */&lt;br /&gt;
public static function mobile_issues_view($args) {&lt;br /&gt;
    global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
    $args = (object) $args;&lt;br /&gt;
    $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
    // Capabilities check.&lt;br /&gt;
    require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
    $context = context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
    require_capability (&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
    if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
        require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
    }&lt;br /&gt;
    $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
    // Get certificates from external (taking care of exceptions).&lt;br /&gt;
    try {&lt;br /&gt;
        $issued = mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
        $certificates = mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
        $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
    } catch (Exception $e) {&lt;br /&gt;
        $issues = [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $data = [&#039;issues&#039; =&amp;gt; $issues];&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
            [&lt;br /&gt;
                &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_issues&#039;, $data),&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
        &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This method for the new page was added just after &amp;lt;code&amp;gt;mobile_course_view&amp;lt;/code&amp;gt;, the code is quite similar: checks the capabilities, retrieves the information required for the template, and returns the template rendered.&lt;br /&gt;
&lt;br /&gt;
The code of the mustache template is also very simple.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_issues.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &amp;lt;%timecreated%&amp;gt; | coreToLocaleString }}&amp;lt;/p&amp;gt;&lt;br /&gt;
                    &amp;lt;p&amp;gt;&amp;lt;%grade%&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As we did in the previous template, in the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache). &lt;br /&gt;
&lt;br /&gt;
Here we are creating an Ionic list that will display a new item in the list per each issued certificated.&lt;br /&gt;
&lt;br /&gt;
For the issued certificated we’ll display the time when it was created (using the app filter &amp;lt;code&amp;gt;coreToLocaleString&amp;lt;/code&amp;gt;). We are also displaying the grade displayed in the certificate (if any).&lt;br /&gt;
&lt;br /&gt;
===Step 5. Plugin webservices, if included===&lt;br /&gt;
If your plugin uses its own web services, they will also need to be enabled for mobile access in your &amp;lt;code&amp;gt;db/services.php&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
The following line should be included in each webservice definition:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;services&#039; =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/db/services.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$functions = [&lt;br /&gt;
    &#039;mod_certificate_get_certificates_by_courses&#039; =&amp;gt; [&lt;br /&gt;
        &#039;classname&#039;     =&amp;gt; &#039;mod_certificate_external&#039;,&lt;br /&gt;
        &#039;methodname&#039;    =&amp;gt; &#039;get_certificates_by_courses&#039;,&lt;br /&gt;
        &#039;description&#039;   =&amp;gt; &#039;Returns a list of certificate instances...&#039;,&lt;br /&gt;
        &#039;type&#039;          =&amp;gt; &#039;read&#039;,&lt;br /&gt;
        &#039;capabilities&#039;  =&amp;gt; &#039;mod/certificate:view&#039;,&lt;br /&gt;
        &#039;services&#039;      =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Mobile.php supported options==&lt;br /&gt;
In the previous section, we learned about some of the existing options for handlers configuration. This is the full list of supported options.&lt;br /&gt;
===Common options===&lt;br /&gt;
* &amp;lt;code&amp;gt;delegate&amp;lt;/code&amp;gt; (mandatory) — Name of the delegate to register the handler in.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (mandatory) — The method to call to retrieve the main page content.&lt;br /&gt;
* &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; (optional) — A method to call to retrieve the initialisation JS and the restrictions to apply to the whole handler. It can also return templates that can be used from JavaScript. You can learn more about this in the [[#Initialisation|Initialisation]] section.&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttocurrentuser&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForUser&amp;lt;/code&amp;gt; function. If true, the handler will only be shown for the current user. For more info about displaying the plugin only for certain users, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttoenrolledcourses&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForCourse&amp;lt;/code&amp;gt; function. If true or not defined, the handler will only be shown for courses the user is enrolled in. For more info about displaying the plugin only for certain courses, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;styles&amp;lt;/code&amp;gt; (optional) — An array with two properties: &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt;. The URL should point to a CSS file, either using an absolute URL or a relative URL. This file will be downloaded and applied by the app. It&#039;s recommended to include styles that will only affect your plugin templates. The version number is used to determine if the file needs to be downloaded again, you should change the version number everytime you change the CSS file.&lt;br /&gt;
* &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; (optional) — If your plugin supports a component in the app different than the one defined by your plugin, you can use this property to specify it. For example, you can create a local plugin to support a certain course format, activity, etc. The component of your plugin in Moodle would be &amp;lt;code&amp;gt;local_whatever&amp;lt;/code&amp;gt;, but in &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; you can specify that this handler will implement &amp;lt;code&amp;gt;format_whatever&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;mod_whatever&amp;lt;/code&amp;gt;. This property was introduced in the version 3.6.1 of the app.&lt;br /&gt;
===Options only for CoreMainMenuDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. Main Menu plugins are always displayed in the More tab, they cannot be displayed as tabs in the bottom bar.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreMainMenuHomeDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseOptionsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ismenuhandler&amp;lt;/code&amp;gt; (optional) — Supported from the 3.7.1 version of the app. Set it to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if you want your plugin to be displayed in the contextual menu of the course instead of in the top tabs. The contextual menu is displayed when you click in the 3-dots button at the top right of the course.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseModuleDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — Path to the module icon. After Moodle app 4.0, this icon is only used as a fallback, the app will always try to use the theme icon so themes can override icons in the app.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (optional) — The function to call to retrieve the main page content. In this delegate the method is optional. If the method is not set, the module won&#039;t be clickable.&lt;br /&gt;
* &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt; (optional) — List of functions to call when prefetching the module. It can be a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; method or a WS. You can filter the params received by the WS. By default, WS will receive these params: &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;. Other valid values that will be added if they are present in the list of params: &amp;lt;code&amp;gt;courseids&amp;lt;/code&amp;gt; (it will receive a list with the courses the user is enrolled in), &amp;lt;code&amp;gt;{component}id&amp;lt;/code&amp;gt; (For example, &amp;lt;code&amp;gt;certificateid&amp;lt;/code&amp;gt;).&lt;br /&gt;
* &amp;lt;code&amp;gt;downloadbutton&amp;lt;/code&amp;gt; (optional) — Whether to display download button in the module. If not defined, the button will be shown if there is any offlinefunction.&lt;br /&gt;
* &amp;lt;code&amp;gt;isresource&amp;lt;/code&amp;gt; (optional) — Whether the module is a resource or an activity. Only used if there is any offline function. If your module relies on the &amp;lt;code&amp;gt;contents&amp;lt;/code&amp;gt; field, then it should be &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;updatesnames&amp;lt;/code&amp;gt; (optional) — Only used if there is any offline function. A regular expression to check if there&#039;s any update in the module. It will be compared to the result of &amp;lt;code&amp;gt;core_course_check_updates&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayopeninbrowser&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Open in browser&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayOpenInBrowser = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydescription&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Description&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayDescription = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayrefresh&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Refresh&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayRefresh = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayprefetch&amp;lt;/code&amp;gt; (optional) — Whether the module should display the download option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayPrefetch = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysize&amp;lt;/code&amp;gt; (optional) — Whether the module should display the downloaded size in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displaySize = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; (optional) — It can be used to specify the supported features of the plugin. Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. It should be an array with features as keys (For example, &amp;lt;code&amp;gt;[FEATURE_NO_VIEW_LINK =&amp;gt; true&amp;lt;/code&amp;gt;). If you need to calculate this dynamically please see [[#Module_plugins:_dynamically_determine_if_a_feature_is_supported|Module plugins: dynamically determine if a feature is supported]]. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; (optional) — If set, this method will be called when the course is rendered and the HTML returned will be displayed in the course page for the module. Please notice the HTML returned should not contain directives or components, only default HTML. Supported from the 3.8 version of the app.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreCourseFormatDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;canviewallsections&amp;lt;/code&amp;gt; (optional) — Whether the course format allows seeing all sections in a single page. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayenabledownload&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, it&#039;s no longer used.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysectionselector&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, use &#039;&#039;displaycourseindex&#039;&#039; instead.&lt;br /&gt;
*&amp;lt;code&amp;gt;displaycourseindex&amp;lt;/code&amp;gt; (optional) — Whether the default course index should be displayed. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
===Options only for CoreUserDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — The type of the addon. The values accepted are &amp;lt;code&amp;gt;&#039;newpage&#039;&amp;lt;/code&amp;gt; (default) and &amp;lt;code&amp;gt;&#039;communication&#039;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreSettingsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
&lt;br /&gt;
===Options only for AddonMessageOutputDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreBlockDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (optional):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section. If this is not supplied, it will default to &amp;lt;code&amp;gt;&#039;plugins.block_{block-name}.pluginname&#039;&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class. If this is not supplied, it will default to &amp;lt;code&amp;gt;block_{block-name}&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — Possible values are:&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;title&amp;quot;&amp;lt;/code&amp;gt; — Your block will only display the block title, and when it&#039;s clicked it will open a new page to display the block contents (the template returned by the block&#039;s method).&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;prerendered&amp;quot;&amp;lt;/code&amp;gt; — Your block will display the content and footer returned by the WebService to get the blocks (for example, &amp;lt;code&amp;gt;core_block_get_course_blocks&amp;lt;/code&amp;gt;), so your block&#039;s method will never be called.&lt;br /&gt;
*** Any other value — Your block will immediately call the method specified in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and it will use the template to render the block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fallback&amp;lt;/code&amp;gt; (optional) — This option allows you to specify a block to use in the app instead of your block. For example, you can make the app display the &amp;quot;My overview&amp;quot; block instead of your block in the app by setting &amp;lt;code&amp;gt;&#039;fallback&#039; =&amp;gt; &#039;myoverview&#039;&amp;lt;/code&amp;gt;. The fallback will only be used if you don&#039;t specify a &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; is different to &amp;lt;code&amp;gt;&#039;title&#039;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&#039;prerendered&#039;&amp;lt;/code&amp;gt;. Supported from the 3.9.0 version of the app.&lt;br /&gt;
==Delegates==&lt;br /&gt;
Delegates can be classified by type of plugin. For more info about type of plugins, please see the [[#Types_of_plugins|Types of plugins]] section.&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new items to the main menu (currently displayed at the bottom of the app). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuHomeDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new tabs in the home page (by default the app is displaying the &amp;quot;Dashboard&amp;quot; and &amp;quot;Site home&amp;quot; tabs). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new options in a course (Participants or Grades are examples of this type of delegate).&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting activity modules or resources.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add additional options in the user profile page in the app.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting course formats. When you open a course from the course list in the mobile app, it will check if there is a &amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt; handler for the format that site uses. If so, it will display the course using that handler. Otherwise, it will use the default app course format.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile course formats]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreSettingsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to add a new option in the settings page.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonMessageOutputDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a message output plugin.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreBlockDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a block. For example, blocks can be displayed in Site Home, Dashboard and the Course page.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question types.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile question types]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionBehaviourDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question behaviours.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting user profile fields.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModQuizAccessRuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a quiz access rule.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModAssignSubmissionDelegate&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;AddonModAssignFeedbackDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use these delegates to support assign submission or feedback plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonWorkshopAssessmentStrategyDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a workshop assessment strategy plugin.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
These delegates require JavaScript to be supported. See [[#Initialisation|Initialisation]] for more information.&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreContentLinksDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreCourseModulePrefetchDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFileUploaderDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CorePluginFileDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFilterDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
==Available components and directives==&lt;br /&gt;
===Difference between components and directives===&lt;br /&gt;
A directive is usually represented as an HTML attribute, allows you to extend a piece of HTML with additional information or functionality. Example of directives are: &amp;lt;code&amp;gt;core-auto-focus&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*ngIf&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ng-repeat&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components are also directives, but they are usually represented as an HTML tag and they are used to add custom elements to the app. Example of components are &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-item&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;core-search-box&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components and directives are Angular concepts; you can learn more about them and the components come out of the box with Ionic in the following links:&lt;br /&gt;
* [https://angular.io/guide/built-in-directives Angular directives documentation]&lt;br /&gt;
* [https://ionicframework.com/docs/components Ionic components]&lt;br /&gt;
===Custom core components and directives===&lt;br /&gt;
These are some useful custom components and directives that are only available in the Moodle App. Please note that this isn’t the full list of custom components and directives, it’s just an extract of the most common ones.&lt;br /&gt;
&lt;br /&gt;
You can find a full list of components and directives in the source code of the app, within [https://github.com/moodlehq/moodleapp/tree/master/src/core/components &amp;lt;code&amp;gt;src/core/components&amp;lt;/code&amp;gt;] and [https://github.com/moodlehq/moodleapp/tree/master/src/core/directives &amp;lt;code&amp;gt;src/core/directives&amp;lt;/code&amp;gt;].&lt;br /&gt;
====&amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;====&lt;br /&gt;
This directive formats the text and adds some directives needed for the app to work as it should. For example, it treats all links and all the embedded media so they work fine in the app. If some content in your template includes links or embedded media, please use this directive.&lt;br /&gt;
&lt;br /&gt;
This directive automatically applies &amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt; to all the links and embedded media.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;text&amp;lt;/code&amp;gt; (string) — The text to format.&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;adaptImg&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to adapt images to screen width. &lt;br /&gt;
* &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether all HTML tags should be removed.&lt;br /&gt;
* &amp;lt;code&amp;gt;singleLine&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether new lines should be removed to display all the text in single line. Only if &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; (number) — Optional. Max height in pixels to render the content box. The minimum accepted value is 50. Using this parameter will force &amp;lt;code&amp;gt;display: block&amp;lt;/code&amp;gt; to calculate the height better. If you want to avoid this, use &amp;lt;code&amp;gt;class=&amp;quot;inline&amp;quot;&amp;lt;/code&amp;gt; at the same time to use &amp;lt;code&amp;gt;display: inline-block&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;fullOnClick&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether it should open a new page with the full contents on click. Only if &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; is set and the content has been collapsed. &lt;br /&gt;
* &amp;lt;code&amp;gt;fullTitle&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;Description&amp;quot;&amp;lt;/code&amp;gt;. Title to use in full view.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-format-text text=&amp;quot;&amp;lt;% cm.description %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle a link. It performs several checks, like checking if the link needs to be opened in the app, and opens the link as it should (without overriding the app).&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;capture&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether the link needs to be captured by the app (check if the link can be handled by the app instead of opening it in a browser).&lt;br /&gt;
* &amp;lt;code&amp;gt;inApp&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to open in an embedded browser within the app or in the system browser.&lt;br /&gt;
* &amp;lt;code&amp;gt;autoLogin&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt;. If the link should be open with auto-login. Accepts the following values:&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;yes&amp;quot;&amp;lt;/code&amp;gt; — Always auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;no&amp;quot;&amp;lt;/code&amp;gt; — Never auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt; — Auto-login only if it points to the current site.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a href=&amp;quot;&amp;lt;% cm.url %&amp;gt;&amp;quot; core-link&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle links to files and embedded files. This directive should be used in any link to a file or any embedded file that you want to have available when the app is offline. &lt;br /&gt;
&lt;br /&gt;
If a file is downloaded, its URL will be replaced by the local file URL.&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;&amp;lt;% event.iconurl %&amp;gt;&amp;quot; core-external-content component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% event.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-user-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to go to user profile on click. When the user clicks the element where this directive is attached, the right user profile will be opened.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;userId&amp;lt;/code&amp;gt; (number) — User id to open the profile.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — Optional. Course id to show the user info related to that course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a ion-item core-user-link userId=&amp;quot;&amp;lt;% userid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to handle a remote file. It shows the file name, icon (depending on mime type) and a button to download or refresh it. The user can identify if the file is downloaded or not based on the button.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;file&amp;lt;/code&amp;gt; (object) — The file. Must have a &amp;lt;code&amp;gt;filename&amp;lt;/code&amp;gt; property and either &amp;lt;code&amp;gt;fileurl&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component the file belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDelete&amp;lt;/code&amp;gt; (boolean) — Optional. Whether the file can be deleted.&lt;br /&gt;
* &amp;lt;code&amp;gt;alwaysDownload&amp;lt;/code&amp;gt; (boolean) — Optional. Whether it should always display the refresh button when the file is downloaded. Use it for files that you cannot determine if they&#039;re outdated or not.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDownload&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether file can be downloaded. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-file&lt;br /&gt;
        [file]=&amp;quot;{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.url %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.name %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            filesize: &#039;&amp;lt;% issue.size %&amp;gt;&#039;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/core-file&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening a file. When the item with this directive is clicked, the file will be downloaded (if needed) and opened.&lt;br /&gt;
&lt;br /&gt;
It is usually recommended to use the &amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt; component since it also displays the state of the file.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt; (object) — The file to download.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. Component ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage (a button to download a file):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button&lt;br /&gt;
        [core-download-file]=&amp;quot;{&lt;br /&gt;
            fileurl: &amp;lt;% issue.url %&amp;gt;,&lt;br /&gt;
            timemodified: &amp;lt;% issue.timemodified %&amp;gt;,&lt;br /&gt;
            filesize: &amp;lt;% issue.size %&amp;gt;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.download | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening the main file of a module.&lt;br /&gt;
&lt;br /&gt;
When the item with this directive is clicked, the whole module will be downloaded (if needed) and its main file opened. This is meant for modules like &amp;lt;code&amp;gt;mod_resource&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This directive must receive either a &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; or a &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. If no files are provided, it will use &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; (object) — Optional, required if module is not supplied. The module object.&lt;br /&gt;
* &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt; (number) — Optional, required if module is not supplied. The module ID.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — The course ID the module belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional, defaults to the same value as &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. Component ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; (object[]) — Optional. List of files of the module. If not provided, uses &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot; &lt;br /&gt;
        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        [files]=&amp;quot;[{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
        }]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-navbar-buttons&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to add buttons to the app&#039;s header without having to place them inside the header itself. Using this component in a site plugin will allow adding buttons to the header of the current page.&lt;br /&gt;
&lt;br /&gt;
If this component indicates a position (start/end), the buttons will only be added if the header has some buttons in that position. If no start/end is specified, then the buttons will be added to the first &amp;lt;code&amp;gt;&amp;lt;ion-buttons&amp;gt;&amp;lt;/code&amp;gt; found in the header.&lt;br /&gt;
&lt;br /&gt;
You can use the &amp;lt;code&amp;gt;[hidden]&amp;lt;/code&amp;gt; input to hide all the inner buttons if a certain condition is met.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons end&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button (click)=&amp;quot;action()&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-icon slot=&amp;quot;icon-only&amp;quot; name=&amp;quot;funnel&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can also use this to add options to the context menu, for example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons&amp;gt;&lt;br /&gt;
    &amp;lt;core-context-menu&amp;gt;&lt;br /&gt;
        &amp;lt;core-context-menu-item&lt;br /&gt;
                [priority]=&amp;quot;500&amp;quot; content=&amp;quot;Nice boat&amp;quot; (action)=&amp;quot;boatFunction()&amp;quot;&lt;br /&gt;
                iconAction=&amp;quot;boat&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/core-context-menu-item&amp;gt;&lt;br /&gt;
    &amp;lt;/core-context-menu&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Using &#039;font&#039; icons with &amp;lt;code&amp;gt;ion-icon&amp;lt;/code&amp;gt;====&lt;br /&gt;
&lt;br /&gt;
Font icons are widely used on the app and Moodle LMS website. In order to support [https://fontawesome.com/v5/search?m=free font awesome icons]. We&#039;ve added a directive that uses prefixes on the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; attribute to use different font icons.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fas-&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;fa-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=solid Font awesome solid] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;far-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=regular Font awesome regular] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fab-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=brands Font awesome brands] library (But only a few are supported and we discourage to use them).&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;moodle-&amp;lt;/code&amp;gt; will use some svg icons [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/moodle imported from Moodle LMS].&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fam-&amp;lt;/code&amp;gt; will use [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/font-awesome customized font awesome icons].&lt;br /&gt;
* If the prefix is not found or not valid, the app will search the icon name on the [https://ionic.io/ionicons ionicons library].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example of usage to show icon &amp;quot;pizza-slice&amp;quot; from font-awesome regular library:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-icon name=&amp;quot;fas-pizza-slice&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We encourage the use of font-awesome icons to match the appearance from the LMS website version.&lt;br /&gt;
&lt;br /&gt;
===Specific component and directives for plugins===&lt;br /&gt;
These are component and directives created specifically for supporting Moodle plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to display a new content when clicked. This new content can be displayed in a new page or in the current page (only if the current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The params to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in v3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in same page or open a new one. &lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that doing &amp;lt;code&amp;gt;[useOtherData]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to go to a new content page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content &lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; &lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to load new content in current page using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot; samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked. The action to do when the WS call is successful depends on the provided data: display a message, go back or refresh current view.&lt;br /&gt;
&lt;br /&gt;
If you want to load a new content when the WS call is done, please see [[#core-site-plugins-call-ws-new-content|&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The params for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;successMessage&amp;lt;/code&amp;gt; (string) — Message to show on success. If not supplied, no message. If supplied but empty, defaults to &amp;quot;Success&amp;quot;.&lt;br /&gt;
* &amp;lt;code&amp;gt;goBackOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to go back if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to refresh the current view if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to send some data to the server without using cache, displaying default messages and refreshing on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage successMessage&lt;br /&gt;
        refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to send some data to the server using cache without confirming, going back on success and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        goBackOnSuccess=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Same as the previous example, but implementing custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;certificateViewed($event)&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.certificateViewed = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked and load a new content passing the WS result as arguments. This new content can be displayed in a new page or in the same page (only if current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to load some new content when done, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The parameters to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in the same page or open a new one.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. The format is the same as in &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;jsData&amp;lt;/code&amp;gt; (any) — JS variables to pass to the new page so they can be used in the template or JS. If &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; is supplied instead of an object, all initial variables from current page will be copied. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;newContentPreSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in 3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to get some data from the server without using cache, showing default confirm and displaying a new page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage&lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to get some data from the server using cache, without confirm, displaying new content in same page and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Same as the previous example, but implementing a custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS as soon as the template is loaded. This directive is meant for actions to do in the background, like calling logging Web Services.&lt;br /&gt;
&lt;br /&gt;
If you want to call a WS when the user clicks on a certain element, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span core-site-plugins-call-ws-on-load&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Advanced features==&lt;br /&gt;
===Display the plugin only if certain conditions are met===&lt;br /&gt;
You might want to display your plugin in the mobile app only if certain dynamic conditions are met, so the plugin would be displayed only for some users. This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section ahead).&lt;br /&gt;
&lt;br /&gt;
All initialisation methods are called as soon as your plugin is retrieved. If you don&#039;t want your plugin to be displayed for the current user, then you should return the following in the initialisation method (only for Moodle site 3.8 and onwards):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;disabled&#039; =&amp;gt; true];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If the Moodle site is older than 3.8, then the initialisation method should return this instead:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;javascript&#039; =&amp;gt; &#039;this.HANDLER_DISABLED&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
On the other hand, you might want to display a plugin only for certain courses (&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;) or only if the user is viewing certain users&#039; profiles (&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;). This can be achieved with the initialisation method too.&lt;br /&gt;
&lt;br /&gt;
In the initialisation method you can return a &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; property with two fields in it: &amp;lt;code&amp;gt;courses&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;users&amp;lt;/code&amp;gt;. If you return a list of courses IDs in this property, then your plugin will only be displayed when the user views any of those courses. In the same way, if you return a list of user IDs then your plugin will only be displayed when the user views any of those users&#039; profiles.&lt;br /&gt;
===Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;===&lt;br /&gt;
The values returned by the functions in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; are added to a variable so they can be used both in JavaScript and in templates. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call is added to a variable named &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt;, while the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call is added to a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call will be passed to the JS and template of all the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; calls in that handler. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call will only be passed to the JS and template returned by that &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call.&lt;br /&gt;
&lt;br /&gt;
This means that, in your JavaScript, you can access and use the data like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.CONTENT_OTHERDATA.myVar;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the template you could use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
{{ CONTENT_OTHERDATA.myVar }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; is the name we put to one of our variables, it can be any name that you want. In the example above, this is the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
[&#039;myVar&#039; =&amp;gt; &#039;Initial value&#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Example====&lt;br /&gt;
In our plugin, we want to display an input text with a certain initial value. When the user clicks a button, we want the value in the input to be sent to a certain Web Service. This can be done using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We will return the initial value of the input in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; of our PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;myVar&#039; =&amp;gt; &#039;My initial value&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then in the template we will use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item text-wrap&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label position=&amp;quot;stacked&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.textlabel | translate }}&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;text&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.myVar&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws name=&amp;quot;mod_certificate_my_webservice&amp;quot; [useOtherDataForWS]=&amp;quot;[&#039;myVar&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we are creating an input text and we use &amp;lt;code&amp;gt;[(ngModel)]&amp;lt;/code&amp;gt; to use the value in &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; as the initial value and to store the changes in the same &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This means that the initial value of the input will be &amp;quot;My initial value&amp;quot;, and if the user changes the value of the input these changes will be applied to the &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This is called 2-way data binding in Angular.&lt;br /&gt;
&lt;br /&gt;
Then we add a button to send this data to a WS, and for that we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive. We use the &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; attribute to specify which variable from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; we want to send to our WebService. So if the user enters &amp;quot;A new value&amp;quot; in the input and then clicks the button, it will call the WebService &amp;lt;code&amp;gt;mod_certificate_my_webservice&amp;lt;/code&amp;gt; and will send as a parameter &amp;lt;code&amp;gt;[&#039;myVar&#039; =&amp;gt; &#039;A new value&#039;]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We can also achieve the same result using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; attribute of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive instead of using &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws &lt;br /&gt;
        name=&amp;quot;mod_certificate_my_webservice&amp;quot; [params]=&amp;quot;{myVar: CONTENT_OTHERDATA.myVar}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The Web Service call will be exactly the same with both versions.&lt;br /&gt;
&lt;br /&gt;
Notice that this example could be done without using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; too, using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; input of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive.&lt;br /&gt;
===Running JS code after a content template has loaded===&lt;br /&gt;
When you return JavaScript code from a handler function using the &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; array key, this code is executed immediately after the web service call returns, which may be before the returned template has been rendered into the DOM. &lt;br /&gt;
&lt;br /&gt;
If your code needs to run after the DOM has been updated, you can use &amp;lt;code&amp;gt;setTimeout&amp;lt;/code&amp;gt; to call it. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&lt;br /&gt;
    &#039;template&#039; =&amp;gt; [&lt;br /&gt;
        // ...&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;javascript&#039; =&amp;gt; &#039;setTimeout(function() { console.log(&amp;quot;DOM is available now&amp;quot;); });&#039;,&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    &#039;files&#039; =&amp;gt; [],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice that if you wanted to write a lot of code here, you might be better off putting it in a function defined in the response from an initialisation template, so that it does not get loaded again with each page of content.&lt;br /&gt;
===JS functions visible in the templates===&lt;br /&gt;
The app provides some JavaScript functions that can be used from the templates to update, refresh or view content. These are the functions:&lt;br /&gt;
* &amp;lt;code&amp;gt;openContent(title: string, args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Open a new page to display some new content. You need to specify the &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; of the new page and the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshContent(showSpinner = true)&amp;lt;/code&amp;gt; — Refresh the current content. By default, it will display a spinner while refreshing. If you don&#039;t want it to be displayed, you should pass &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; as a parameter.&lt;br /&gt;
* &amp;lt;code&amp;gt;updateContent(args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Refresh the current content using different parameters. You need to specify the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Group selector=====&lt;br /&gt;
Imagine we have an activity that uses groups and we want to let the user select which group they want to see. A possible solution would be to return all the groups in the same template (hidden), and then show the group user selects. However, we can make it more dynamic and return only the group the user is requesting.&lt;br /&gt;
&lt;br /&gt;
To do so, we&#039;ll use a drop down to select the group. When the user selects a group using this drop down, we&#039;ll update the page content to display the new group.&lt;br /&gt;
&lt;br /&gt;
The main difficulty in this is to tell the view which group needs to be selected when the view is loaded. There are 2 ways to do it: using plain HTML or using Angular&#039;s &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;.&lt;br /&gt;
======Using plain HTML======&lt;br /&gt;
We need to add a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; attribute to the option that needs to be selected. To do so, we need to pre-caclulate the selected option in the PHP code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// Detect which group is selected.&lt;br /&gt;
foreach ($groups as $gid=&amp;gt;$group) {&lt;br /&gt;
    $group-&amp;gt;selected = $gid === $groupid;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$data = [&lt;br /&gt;
    &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
    &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
    &#039;groups&#039; =&amp;gt; $groups,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the code above, we&#039;re retrieving the groups the user can see and then we&#039;re adding a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; boolean to each one to determine which one needs to be selected in the drop down. Finally, we pass the list of groups to the template.&lt;br /&gt;
&lt;br /&gt;
In the template, we display the drop down like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: $event})&amp;quot; interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot; &amp;lt;%#selected%&amp;gt;selected&amp;lt;%/selected%&amp;gt; &amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The &amp;lt;code&amp;gt;ionChange&amp;lt;/code&amp;gt; function will be called every time the user selects a different group with the drop down. We&#039;re using the &amp;lt;code&amp;gt;updateContent&amp;lt;/code&amp;gt; function to update the current view using the new group. &amp;lt;code&amp;gt;$event&amp;lt;/code&amp;gt; is an Angular variable that will have the selected value (in our case, the group ID that was just selected). This is enough to make the group selector work.&lt;br /&gt;
======Using &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;======&lt;br /&gt;
&amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; is an Angular directive that allows storing the value of a certain input or select in a JavaScript variable, and also the opposite way: tell the input or select which value to set. The main problem is that we cannot initialise a JavaScript variable from the template, so we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the PHP function we&#039;ll return the group that needs to be selected in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
        &#039;group&#039; =&amp;gt; $groupid,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we don&#039;t need to iterate over the groups array like in the plain HTML example. However, now we&#039;re returning the group id in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array. As it&#039;s explained in the [[#Using_otherdata|Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;]] section, this &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; is visible in the templates inside a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. So in the template we&#039;ll use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.group&amp;quot;&lt;br /&gt;
        (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: CONTENT_OTHERDATA.group})&amp;quot;&lt;br /&gt;
        interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Use the rich text editor===&lt;br /&gt;
The rich text editor included in the app requires a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt; to work. You can use the &amp;lt;code&amp;gt;FormBuilder&amp;lt;/code&amp;gt; library to create this control (or to create a whole &amp;lt;cide&amp;gt;FormGroup if you prefer).&lt;br /&gt;
&lt;br /&gt;
With the following JavaScript you&#039;ll be able to create a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.control = this.FormBuilder.control(this.CONTENT_OTHERDATA.rte);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we&#039;re using a value returned in &amp;lt;code&amp;gt;OTHERDATA&amp;lt;/code&amp;gt; as the initial value of the rich text editor, but you can use whatever you want.&lt;br /&gt;
&lt;br /&gt;
Then you need to pass this control to the rich text editor in your template:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;core-rich-text-editor item-content [control]=&amp;quot;control&amp;quot; placeholder=&amp;quot;Enter your text here&amp;quot; name=&amp;quot;rte_answer&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-rich-text-editor&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, there are several ways to send the value in the rich text editor to a Web Service to save it. This is one of the simplest options:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_webservice&amp;quot; [params]=&amp;quot;{rte: control.value}&amp;quot; ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re passing the value of the rich text editor as a parameter to our Web Service.&lt;br /&gt;
===Initialisation===&lt;br /&gt;
All handlers can specify an &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; method in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. This method is meant to return some JavaScript code that needs to be executed as soon as the plugin is retrieved.&lt;br /&gt;
&lt;br /&gt;
When the app retrieves all the handlers, the first thing it will do is call the &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt; Web Service with the initialisation method. This WS call will only receive the default arguments.&lt;br /&gt;
&lt;br /&gt;
The app will immediately execute the JavaScript code returned by this WS call. This JavaScript can be used to manually register your handlers in the delegates you want, without having to rely on the default handlers built based on the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; data.&lt;br /&gt;
&lt;br /&gt;
The templates returned by this method will be added to a &amp;lt;code&amp;gt;INIT_TEMPLATES&amp;lt;/code&amp;gt; variable that will be passed to all the JavaScript code of that handler. This means that the JavaScript returned by the initialisation method or the main method can access any of the templates HTML like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.INIT_TEMPLATES[&#039;main&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In this case, &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is the ID of the template we want to use.&lt;br /&gt;
&lt;br /&gt;
The same happens with the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the initialisation method, it is added to an &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt; variable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; field returned by this call will be used to determine if your handler is enabled or not. For example, if your handler is for the delegate &amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt; and you return a list of course ids in &amp;lt;code&amp;gt;restrict.courses&amp;lt;/code&amp;gt;, then your handler will only be enabled in the courses you returned. This only applies to the default handlers, if you register your own handler using the JavaScript code then you should check yourself if the handler is enabled.&lt;br /&gt;
&lt;br /&gt;
Finally, if you return an object in this initialisation JavaScript code, all the properties of that object will be passed to all the JavaScript code of that handler so you can use them when the code is run. For example, if your JavaScript code does something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    MyAddonClass: new MyAddonClass()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then, for the rest of JavaScript code of your handler (for example, the main method) you can use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.MyAddonClass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Link handlers=====&lt;br /&gt;
A link handler allows you to decide what to do when a link with a certain URL is clicked. This is useful, for example, to open your plugin page when a link to your plugin is clicked.&lt;br /&gt;
&lt;br /&gt;
After the 4.0 version, the Moodle app automatically creates two link handlers for module plugins, you don&#039;t need to create them in your plugin&#039;s Javascript code anymore:&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginanme/view.php?id=X&#039;&#039;. When this link is clicked, it will open your module in the app.&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginname/index.php?id=X&#039;&#039;. When this link is clicked, it will open a page in the app listing all the modules of your type inside a certain course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Link handlers have some advanced features that allow you to change how links behave under different conditions.&lt;br /&gt;
======Patterns======&lt;br /&gt;
You can define a Regular Expression pattern to match certain links. This will apply the handler only to links that match the pattern.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = RegExp(&#039;\/mod\/foo\/specialpage.php&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Priority======&lt;br /&gt;
Multiple link handlers may apply to a given link. You can define the order of precedence by setting the priority; the handler with the highest priority will be used.&lt;br /&gt;
&lt;br /&gt;
All default handlers have a priority of 0, so 1 or higher will override the default.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Multiple actions======&lt;br /&gt;
Once a link has been matched, the handler&#039;s &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method determines what the link should do. This method has access to the URL and its parameters.&lt;br /&gt;
&lt;br /&gt;
Different actions can be returned depending on different conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                action: function(siteId, navCtrl) {&lt;br /&gt;
                    // The actual behaviour of the link goes here.&lt;br /&gt;
                },&lt;br /&gt;
                sites: [&lt;br /&gt;
                    // ...&lt;br /&gt;
                ],&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                // ...&lt;br /&gt;
            },&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once handlers have been matched for a link, the actions will be fetched for all the matching handlers, in priorty order. The first valid action will be used to open the link.&lt;br /&gt;
&lt;br /&gt;
If your handler is matched with a link, but a condition assessed in the &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method means you want to revert to the next highest priorty handler, you can invalidate your action by settings its sites propety to an empty array.&lt;br /&gt;
======Complex example======&lt;br /&gt;
This will match all URLs containing &amp;lt;code&amp;gt;/mod/foo/&amp;lt;/code&amp;gt;, and force those with an id parameter that&#039;s not in the &amp;lt;code&amp;gt;supportedModFoos&amp;lt;/code&amp;gt; array to open in the user&#039;s browser, rather than the app.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const that = this;&lt;br /&gt;
const supportedModFoos = [...];&lt;br /&gt;
&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = new RegExp(&#039;\/mod\/foo\/&#039;);&lt;br /&gt;
        this.name = &#039;AddonModFooLinkHandler&#039;;&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {     &lt;br /&gt;
        const action = {&lt;br /&gt;
            action() {&lt;br /&gt;
                that.CoreUtilsProvider.openInBrowser(url);&lt;br /&gt;
            },&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (supportedModFoos.indexOf(parseInt(params.id)) !== -1) {&lt;br /&gt;
            action.sites = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return [action];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreContentLinksDelegate.registerHandler(new AddonModFooLinkHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Module prefetch handler=====&lt;br /&gt;
The &amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt; handler allows you to define a list of offline functions to prefetch a module. However, you might want to create your own prefetch handler to determine what needs to be downloaded. For example, you might need to chain WS calls (pass the result of a WS call to the next one), and this cannot be done using offline functions.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to create a prefetch handler using the initialisation JS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Create a class that extends from CoreCourseActivityPrefetchHandlerBase.&lt;br /&gt;
class AddonModCertificateModulePrefetchHandler extends CoreCourseActivityPrefetchHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.name = &#039;AddonModCertificateModulePrefetchHandler&#039;;&lt;br /&gt;
        this.modName = &#039;certificate&#039;;&lt;br /&gt;
&lt;br /&gt;
        // This must match the plugin identifier from db/mobile.php,&lt;br /&gt;
        // otherwise the download link in the context menu will not update correctly.&lt;br /&gt;
        this.component = &#039;mod_certificate&#039;;&lt;br /&gt;
        this.updatesNames = /^configuration$|^.*files$/;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Override the prefetch call.&lt;br /&gt;
    prefetch(module, courseId, single, dirPath) {&lt;br /&gt;
        return this.prefetchPackage(module, courseId, single, prefetchCertificate);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prefetchCertificate(module, courseId, single, siteId) {&lt;br /&gt;
    // Perform all the WS calls.&lt;br /&gt;
    // You can access most of the app providers using that.ClassName. E.g. that.CoreWSProvider.call().&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseModulePrefetchDelegate.registerHandler(new AddonModCertificateModulePrefetchHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
One relatively simple full example is where you have a function that needs to work offline, but it has an additional argument other than the standard ones. You can imagine for this an activity like the book module, where it has multiple pages for the same &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;. The app will not automatically work with this situation — it will call the offline function with the standard arguments only — so you won&#039;t be able to prefetch all the possible parameters.&lt;br /&gt;
&lt;br /&gt;
To deal with this, you need to implement a web service in your Moodle component that returns the list of possible extra arguments, and then you can call this web service and loop around doing the same thing the app does when it prefetches the offline functions. Here is an example from a third-party module (showing only the actual prefetch function, the rest of the code is as above) where there are multiple values of a custom &amp;lt;code&amp;gt;section&amp;lt;/code&amp;gt; parameter for the mobile function &amp;lt;code&amp;gt;mobile_document_view&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function prefetchOucontent(module, courseId, single, siteId) {&lt;br /&gt;
    var component = &#039;mod_oucontent&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Get the site, first.&lt;br /&gt;
    return that.CoreSitesProvider.getSite(siteId).then(function(site) {&lt;br /&gt;
        // Read the list of pages in this document using a web service.&lt;br /&gt;
        return site.read(&#039;mod_oucontent_get_page_list&#039;, {&#039;cmid&#039;: module.id}).then(function(response) {&lt;br /&gt;
            var promises = [];&lt;br /&gt;
&lt;br /&gt;
            // For each page, read and process the page - this is a copy of logic in the app at&lt;br /&gt;
            // siteplugins.ts (prefetchFunctions), but modified to add the custom argument.&lt;br /&gt;
            for(var i = 0; i &amp;lt; response.length; i++) {&lt;br /&gt;
                var args = {&lt;br /&gt;
                    courseid: courseId,&lt;br /&gt;
                    cmid: module.id,&lt;br /&gt;
                    userid: site.getUserId()&lt;br /&gt;
                };&lt;br /&gt;
                if (response[i] !== &#039;&#039;) {&lt;br /&gt;
                    args.section = response[i];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                promises.push(that.CoreSitePluginsProvider.getContent(&lt;br /&gt;
                        component, &#039;mobile_document_view&#039;, args).then(&lt;br /&gt;
                        function(result) {&lt;br /&gt;
                            var subPromises = [];&lt;br /&gt;
                            if (result.files &amp;amp;&amp;amp; result.files.length) {&lt;br /&gt;
                                subPromises.push(that.CoreFilepoolProvider.downloadOrPrefetchFiles(&lt;br /&gt;
                                        site.id, result.files, true, false, component, module.id));&lt;br /&gt;
                            }&lt;br /&gt;
                            return Promise.all(subPromises);&lt;br /&gt;
                        }));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(promises);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Single activity course format=====&lt;br /&gt;
In the following example, the value of &amp;lt;code&amp;gt;INIT_TEMPLATES[&#039;main&#039;]&amp;lt;/code&amp;gt; is:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;componentClass&amp;quot; [data]=&amp;quot;data&amp;quot;&amp;gt;&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This template is returned by the initialisation method. And this is the JavaScript code returned:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatComponent {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.data = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ngOnChanges(changes) {&lt;br /&gt;
        var self = this;&lt;br /&gt;
&lt;br /&gt;
        if (this.course &amp;amp;&amp;amp; this.sections &amp;amp;&amp;amp; this.sections.length) {&lt;br /&gt;
            var module = this.sections[0] &amp;amp;&amp;amp; this.sections[0].modules &amp;amp;&amp;amp; this.sections[0].modules[0];&lt;br /&gt;
            if (module &amp;amp;&amp;amp; !this.componentClass) {&lt;br /&gt;
                that.CoreCourseModuleDelegate.getMainComponent(that.Injector, this.course, module).then((component) =&amp;gt; {&lt;br /&gt;
                    self.componentClass = component || that.CoreCourseUnsupportedModuleComponent;&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.data.courseId = this.course.id;&lt;br /&gt;
            this.data.module = module;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    doRefresh(refresher, done) {&lt;br /&gt;
        return Promise.resolve(this.dynamicComponent.callComponentFunction(&amp;quot;doRefresh&amp;quot;, [refresher, done]));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatHandler {&lt;br /&gt;
    &lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.name = &#039;singleactivity&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    isEnabled() {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canViewAllSections() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseTitle(course, sections) {&lt;br /&gt;
        if (sections &amp;amp;&amp;amp; sections[0] &amp;amp;&amp;amp; sections[0].modules &amp;amp;&amp;amp; sections[0].modules[0]) {&lt;br /&gt;
            return sections[0].modules[0].name;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return course.fullname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displayEnableDownload() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displaySectionSelector() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseFormatComponent() {&lt;br /&gt;
        return that.CoreCompileProvider.instantiateDynamicComponent(that.INIT_TEMPLATES[&#039;main&#039;], AddonSingleActivityFormatComponent);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseFormatDelegate.registerHandler(new AddonSingleActivityFormatHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using the JavaScript API===&lt;br /&gt;
The JavaScript API is only supported by the delegates specified in the [[#Templates_downloaded_on_login_and_rendered_using_JS_data_2|Templates downloaded on login and rendered using JS data]] section. This API allows you to override any of the functions of the default handler.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; specified in a handler registered in the &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; will be called immediately after the initialisation method, and the JavaScript returned by this method will be run. If this JavaScript code returns an object with certain functions, these functions will override the ones in the default handler.&lt;br /&gt;
&lt;br /&gt;
For example, if the JavaScript returned by the method returns something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The the &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function of the default handler will be overridden by the returned &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
The default handler for &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; only has 2 functions: &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt;. In addition, the JavaScript code can return an extra function named &amp;lt;code&amp;gt;componentInit&amp;lt;/code&amp;gt; that will be executed when the component returned by &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; is initialised.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to support the text user profile field using this API:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
var result = {&lt;br /&gt;
    componentInit: function() {&lt;br /&gt;
        if (this.field &amp;amp;&amp;amp; this.edit &amp;amp;&amp;amp; this.form) {&lt;br /&gt;
            this.field.modelName = &#039;profile_field_&#039; + this.field.shortname;&lt;br /&gt;
&lt;br /&gt;
            if (this.field.param2) {&lt;br /&gt;
                this.field.maxlength = parseInt(this.field.param2, 10) || &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.field.inputType = that.CoreUtilsProvider.isTrueOrOne(this.field.param3) ? &#039;password&#039; : &#039;text&#039;;&lt;br /&gt;
&lt;br /&gt;
            var formData = {&lt;br /&gt;
                value: this.field.defaultdata,&lt;br /&gt;
                disabled: this.disabled,&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            this.form.addControl(this.field.modelName,&lt;br /&gt;
                that.FormBuilder.control(formData, this.field.required &amp;amp;&amp;amp; !this.field.locked ? that.Validators.required : null));&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        var name = &#039;profile_field_&#039; + field.shortname;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            type: &amp;quot;text&amp;quot;,&lt;br /&gt;
            name: name,&lt;br /&gt;
            value: that.CoreTextUtilsProvider.cleanTags(formValues[name]),&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Translate dynamic strings===&lt;br /&gt;
If you wish to have an element that displays a localised string based on value from your template you can doing something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_myactivity.&amp;lt;% status %&amp;gt;&#039; | translate }}&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This could save you from having to write something like when only one value should be displayed:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        &amp;lt;%#isedting%&amp;gt;{{ &#039;plugin.mod_myactivity.editing&#039; | translate }}&amp;lt;%/isediting%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isopen%&amp;gt;{{ &#039;plugin.mod_myactivity.open&#039; | translate }}&amp;lt;%/isopen%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isclosed%&amp;gt;{{ &#039;plugin.mod_myactivity.closed&#039; | translate }}&amp;lt;%/isclosed%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using strings with dates===&lt;br /&gt;
If you have a string that you wish to pass a formatted date, for example in the Moodle language file you have:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;strwithdate&#039;] = &#039;This string includes a date of {$a-&amp;gt;date} in the middle of it.&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can localise the string correctly in your template using something like the following:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{ &#039;plugin.mod_myactivity.strwithdate&#039; | translate: {$a: { date: &amp;lt;% timestamp %&amp;gt; * 1000 | coreFormatDate: &amp;quot;dffulldate&amp;quot; } } }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A Unix timestamp must be multiplied by 1000 as the Mobile App expects millisecond timestamps, whereas Unix timestamps are in seconds.&lt;br /&gt;
===Support push notification clicks===&lt;br /&gt;
If your plugin sends push notifications to the app, you might want to open a certain page in the app when the notification is clicked. There are several ways to achieve this.&lt;br /&gt;
&lt;br /&gt;
The easiest way is to include a &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; in your notification. When the notification is clicked, the app will try to open the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; will also be displayed in web. If you want to use a specific URL for the app, different than the one displayed in web, you can do so by returning a &amp;lt;code&amp;gt;customdata&amp;lt;/code&amp;gt; array that contains an &amp;lt;code&amp;gt;appurl&amp;lt;/code&amp;gt; property:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$notification-&amp;gt;customdata = [&lt;br /&gt;
    &#039;appurl&#039; =&amp;gt; $myurl-&amp;gt;out(),&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In both cases you will have to create a link handler to treat the URL. For more info on how to create the link handler, please see [[#Advanced_link_handler|how to create an advanced link handler]].&lt;br /&gt;
&lt;br /&gt;
If you want to do something that only happens when the notification is clicked, not when the link is clicked, you&#039;ll have to implement a push click handler yourself. The way to create it is similar to [[#Advanced_link_handler|creating an advanced link handler]], but you&#039;ll have to use &amp;lt;code&amp;gt;CorePushNotificationsDelegate&amp;lt;/code&amp;gt; and your handler will have to implement the properties and functions defined in the [https://github.com/moodlehq/moodleapp/blob/master/src/core/features/pushnotifications/services/push-delegate.ts#L27 CorePushNotificationsClickHandler] interface.&lt;br /&gt;
===Implement a module similar to mod_label===&lt;br /&gt;
In Moodle 3.8 or higher, if your plugin doesn&#039;t support &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt; and you don&#039;t specify a &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; then the module will only display the module description in the course page and it won&#039;t be clickable in the app, just like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt;. You can decide if you want the module icon to be displayed or not (if you don&#039;t want it to be displayed, then don&#039;t define it in &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
However, if your plugin needs to work in previous versions of Moodle or you want to display something different than the description then you need a different approach.&lt;br /&gt;
&lt;br /&gt;
If your plugin wants to render something in the course page instead of just the module name and description you should specify the &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. The template returned by this method will be rendered in the course page. Please notice the HTML returned should not contain directives or components, only plain HTML.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want your module to be clickable then you just need to remove &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. With these 2 changes you can have a module that behaves like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt; in the app.&lt;br /&gt;
===Use Ionic navigation lifecycle functions===&lt;br /&gt;
Ionic let pages define some functions that will be called when certain navigation lifecycle events happen. For more info about these functions, see [https://ionicframework.com/docs/api/router-outlet Ionic&#039;s documentation].&lt;br /&gt;
&lt;br /&gt;
You can define these functions in your plugin javascript:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.ionViewWillLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In addition to that, you can also implement &amp;lt;code&amp;gt;canLeave&amp;lt;/code&amp;gt; to use Angular route guards:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.canLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So for example you can make your plugin ask for confirmation if the user tries to leave the page when he has some unsaved data.&lt;br /&gt;
===Module plugins: dynamically determine if a feature is supported===&lt;br /&gt;
In Moodle you can specify if your plugin supports a certain feature, like &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. If your plugin will always support or not a certain feature, then you can use the &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; to specify it ([[#Options_only_for_CoreCourseModuleDelegate|see more documentation about this]]). But if you need to calculate it dynamically then you will have to create a function to calculate it. &lt;br /&gt;
&lt;br /&gt;
This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section above). The JavaScript returned by your initialisation method will need to define a function named &amp;lt;code&amp;gt;supportsFeature&amp;lt;/code&amp;gt; that will receive the name of the feature:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    supportsFeature: function(featureName) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;.&lt;br /&gt;
== Testing ==&lt;br /&gt;
You can also write automated tests for your plugin using Behat, you can read more about it on the [[Acceptance testing for the Moodle App]] page.&lt;br /&gt;
== Upgrading plugins from an older version ==&lt;br /&gt;
If you added mobile support to your plugin for the Ionic 3 version of the app (previous to the 3.9.5 release), you will probably need to make some changes to make it compatible with Ionic 5.&lt;br /&gt;
&lt;br /&gt;
Learn more at the [[Moodle App Plugins Upgrade Guide]].&lt;br /&gt;
==Troubleshooting==&lt;br /&gt;
=== Invalid response received ===&lt;br /&gt;
You might receive this error when using the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive or similar. By default, the app expects all Web Service calls to return an object, if your Web Service returns another type (string, boolean, etc.) then you need to specify it using the &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; attribute of the directive. For example, if your WS returns a boolean value, then you should specify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{typeExpected: &#039;boolean&#039;}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In a similar way, if your Web Service returns &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; you need to tell the app not to expect any result using &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{responseExpected: false}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS ===&lt;br /&gt;
Some directives allow you to specify a form id or name to send the data from the form to a certain WS. These directives look for HTML inputs to retrieve the data to send. However, &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; don&#039;t use HTML inputs, they simulate them, so the directive isn&#039;t going to find their data and so it won&#039;t be sent to the Web Service.&lt;br /&gt;
&lt;br /&gt;
There are 2 workarounds to fix this problem.&lt;br /&gt;
==== Sending the data manually ====&lt;br /&gt;
The first solution is to send the missing params manually using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; property. We will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input value in a variable, and this variable will be passed to the parameters. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a template like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group name=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, responses: responses}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Basically, you need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to the affected element (in this case, the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt;). You can put whatever name you want as the value, we used &amp;quot;responses&amp;quot;. With this, every time the user selects a radio button the value will be stored in a variable called &amp;quot;responses&amp;quot;. Then, in the button we are passing this variable to the parameters of the Web Service.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute has priority over &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;, so if you have an input with &amp;lt;code&amp;gt;name=&amp;quot;responses&amp;quot;&amp;lt;/code&amp;gt; it will override what you&#039;re manually passing to &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;.&lt;br /&gt;
==== Using a hidden input ====&lt;br /&gt;
Since the directive is looking for HTML inputs, you need to add one with the value to send to the server. You can use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to synchronise your radio/checkbox/select with the new hidden input. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a radio button like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot; [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;hidden&amp;quot; [ngModel]=&amp;quot;responses&amp;quot; name=&amp;quot;responses&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we&#039;re using a variable called &amp;quot;responses&amp;quot; to synchronise the data between the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt; and the hidden input. You can use whatever name you want.&lt;br /&gt;
=== I can&#039;t return an object or array in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; ===&lt;br /&gt;
If you try to return an object or an array in any field inside &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, the Web Service call will fail with the following error:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Scalar type expected, array or object received&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Each field in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; must be a string, number or boolean; it cannot be an object or array. To make it work, you need to encode your object or array into a JSON string:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($data)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app will automatically parse this JSON and convert it back into an array or object.&lt;br /&gt;
==Examples==&lt;br /&gt;
===Accepting dynamic names in a Web Service===&lt;br /&gt;
We want to display a form where the names of the fields are dynamic, like it happens in quiz. This data will be sent to a new Web Service that we have created.&lt;br /&gt;
&lt;br /&gt;
The first issue we find is that the Web Service needs to define the names of the parameters received, but in this case they&#039;re dynamic. The solution is to accept an array of objects with name and value. So in the &amp;lt;code&amp;gt;_parameters()&amp;lt;/code&amp;gt; function of our new Web Service, we will add this parameter:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;data&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
     new external_single_structure(&lt;br /&gt;
        [&lt;br /&gt;
            &#039;name&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data name&#039;),&lt;br /&gt;
            &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data value&#039;),&lt;br /&gt;
        ]&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;The data to be saved&#039;, VALUE_DEFAULT, []&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now we need to adapt our form to send the data as the Web Service requires it. In our template, we have a button with the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive that will send the form data to our Web Service. To make this work we will have to pass the parameters manually, without using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute, because we need to format the data before it is sent.&lt;br /&gt;
&lt;br /&gt;
Since we will send the parameters manually and we want it all to be sent in the same array, we will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input data into a variable that we&#039;ll call &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;, but you can use the name you want. This variable will be an object that will hold the input data with the format &amp;quot;name-&amp;gt;value&amp;quot;. For example, if I have an input with name &amp;quot;a1&amp;quot; and value &amp;quot;My answer&amp;quot;, the data object will be:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{a1: &#039;My answer&#039;}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So we need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to all the inputs whose values need to be sent to the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; WS param. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-input name=&amp;quot;&amp;lt;% name %&amp;gt;&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.data[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re using &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; to store the data. We do it like this because we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the form, setting the values the user has already stored. If you don&#039;t need to initialise the form, then you can use the &amp;lt;code&amp;gt;dataObject&amp;lt;/code&amp;gt; variable, an empty object that the mobile app creates for you:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[(ngModel)]=&amp;quot;dataObject[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app has a function that allows you to convert this data object into an array like the one the WS expects: &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt;. So in our button we&#039;ll use this function to format the data before it&#039;s sent:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_ws_name&amp;quot;&lt;br /&gt;
    [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, data: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.data, &#039;name&#039;, &#039;value&#039;)}&amp;quot;&lt;br /&gt;
    successMessage&lt;br /&gt;
    refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see in the example above, we&#039;re specifying that the keys of the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object need to be stored in a property called &amp;quot;name&amp;quot;, and the values need to be stored in a property called &amp;quot;value&amp;quot;. If your Web Service expects different names you need to change the parameters of the &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
If you open your plugin now in the app it will display an error in the JavaScript console. The reason is that the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; variable doesn&#039;t exist inside &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. As it is explained in previous sections, &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; holds the data that you return in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; for your method. We&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the values to be displayed in the form.&lt;br /&gt;
&lt;br /&gt;
If the user hasn&#039;t answered the form yet, we can initialise the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object as an empty object. Please remember that we cannot return arrays or objects in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, so we&#039;ll return a JSON string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; &#039;{}&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
With the code above, the form will always be empty when the user opens it. But now we want to check if the user has already answered the form and fill the form with the previous values. We will do it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$userdata = get_user_responses(); // It will held the data in a format name-&amp;gt;value. Example: [&#039;a1&#039; =&amp;gt; &#039;My value&#039;].&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($userdata)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now the user will be able to see previous values when the form is opened, and clicking the button will send the data to our Web Service in array format.&lt;br /&gt;
==Moodle plugins with mobile support==&lt;br /&gt;
* Group choice: [https://moodle.org/plugins/mod_choicegroup Moodle plugins directory entry] and [https://github.com/ndunand/moodle-mod_choicegroup code in github].&lt;br /&gt;
* Custom certificate: [https://moodle.org/plugins/mod_customcert Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_customcert code in github].&lt;br /&gt;
* Gapfill question type: [https://moodle.org/plugins/qtype_gapfill Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_gapfill in github].&lt;br /&gt;
* Wordselect question type: [https://moodle.org/plugins/qtype_wordselect Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_wordselect in github].&lt;br /&gt;
* RegExp question type: [https://moodle.org/plugins/qtype_regexp Moodle plugins directory entry] and [https://github.com/rezeau/moodle-qtype_regexp in github].&lt;br /&gt;
* Certificate: [https://moodle.org/plugins/mod_certificate Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_certificate in github].&lt;br /&gt;
* Attendance [https://moodle.org/plugins/mod_attendance Moodle plugins directory entry] and [https://github.com/danmarsden/moodle-mod_attendance in github].&lt;br /&gt;
* ForumNG (unfinished support) [https://moodle.org/plugins/mod_forumng Moodle plugins directory entry] and [https://github.com/moodleou/moodle-mod_forumng in github].&lt;br /&gt;
* News block [https://github.com/moodleou/moodle-block_news in github].&lt;br /&gt;
* H5P activity module [https://moodle.org/plugins/mod_hvp Moodle plugins directory entry] and [https://github.com/h5p/h5p-moodle-plugin in github].&lt;br /&gt;
See the complete list in [https://moodle.org/plugins/browse.php?list=award&amp;amp;id=6 the plugins database] (it may contain some outdated plugins).&lt;br /&gt;
=== Mobile app support award ===&lt;br /&gt;
If you want your plugin to be awarded in the plugins directory and marked as supporting the mobile app, please feel encouraged to contact us via email at [mailto:mobile@moodle.com mobile@moodle.com].&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to include a link to your plugin page and the location of its code repository.&lt;br /&gt;
&lt;br /&gt;
See [https://moodle.org/plugins/?q=award:mobile-app the list of awarded plugins] in the plugins directory.&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Moodle App Ionic 5]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62059</id>
		<title>Moodle App Plugins Development Guide</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62059"/>
		<updated>2022-04-25T08:23:43Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: /* Options only for CoreUserDelegate */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle App (Ionic 5)}}&lt;br /&gt;
If you want to add mobile support to your Moodle plugin, you can achieve it by extending different areas of the app using &#039;&#039;just PHP server side code&#039;&#039; and providing templates written with [https://ionicframework.com/docs/components Ionic] and custom components.&lt;br /&gt;
&lt;br /&gt;
You will have to:&lt;br /&gt;
# Create a &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file in your plugin. In this file, you will be able to indicate which areas of the app you want to extend. For example, adding a new option in the main menu, implementing support for a new activity module, including a new option in the course menu, including a new option in the user profile, etc. All the areas supported are described further in this document.&lt;br /&gt;
# Create new functions in a reserved namespace that will return the content of the new options. The content should be returned rendered as html. This html should use Ionic components so that it looks native, but it can be generated using mustache templates.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let’s clarify some points:&lt;br /&gt;
* You don’t need to create new Web Service functions (although you will be able to use them for advanced features). You just need plain php functions that will be placed in a reserved namespace.&lt;br /&gt;
* Those functions will be exported via the Web Service function &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt;.&lt;br /&gt;
* As arguments of your functions you will always receive the &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, some relevant details of the app (like the app version or the current language in the app), and some specific data depending on the type of plugin (&amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
* The mobile app also implements a list of custom Ionic components and directives that provide dynamic behaviour; like indicating that you are linking a file that can be downloaded, allowing a transition to new pages into the app calling a specific function in the server, submitting form data to the server, etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Getting started==&lt;br /&gt;
If you only want to write a plugin, it is not necessary that you set up your environment to work with the Moodle App. In fact, you don&#039;t even need to compile it. You can just [[Using the Moodle App in a browser|use a Chromium-based browser]] to add mobile support to your plugins!&lt;br /&gt;
&lt;br /&gt;
You can use the app from one of the hosted versions on [https://master.apps.moodledemo.net master.apps.moodledemo.net] (the latest stable version) and [https://integration.apps.moodledemo.net integration.apps.moodledemo.net] (the latest development version). If you need any specific environment (hosted versions are deployed with a &#039;&#039;production&#039;&#039; environment), you can also use [[Moodle App Docker Images|Docker images]]. And if you need to test your plugin in a native device, you can always use [https://download.moodle.org/mobile Moodle HQ&#039;s application].&lt;br /&gt;
&lt;br /&gt;
This should suffice for developing plugins. However, if you are working on advanced functionality and you need to run the application from the source code, you can find more information in the [[Moodle App Development Guide]].&lt;br /&gt;
===Development workflow===&lt;br /&gt;
Before getting into the specifics of your plugin, we recommend that you start adding a simple &amp;quot;Hello World&amp;quot; button in the app to see that everything works properly.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s say your plugin is called &amp;lt;code&amp;gt;local_hello&amp;lt;/code&amp;gt;, you can start by adding the following files:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;local_hello&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [&lt;br /&gt;
            &#039;hello&#039; =&amp;gt; [&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreMainMenuDelegate&#039;,&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;view_hello&#039;,&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;title&#039; =&amp;gt; &#039;hello&#039;,&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; &#039;earth&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [&lt;br /&gt;
            [&#039;hello&#039;, &#039;local_hello&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace local_hello\output;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    public static function view_hello() {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; &#039;&amp;lt;h1 class=&amp;quot;text-center&amp;quot;&amp;gt;{{ &amp;quot;plugin.local_hello.hello&amp;quot; | translate }}&amp;lt;/h1&amp;gt;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;lang/en/local_hello.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;hello&#039;] = &#039;Hello World&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once you&#039;ve done that, try logging into your site in the app and you should see a new button in the main menu or more menu (depending on the device) saying &amp;quot;Hello World&amp;quot;. If you press this button, you should see a page saying &amp;quot;Hello World!&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Congratualtions, you have written your first Moodle plugin with moodle support!&lt;br /&gt;
&lt;br /&gt;
You can read the rest of this page to learn more about mobile plugins and start working on your plugin. Here&#039;s some things to keep in mind:&lt;br /&gt;
* If you change the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file, you will have to refresh the browser. And remember to [https://developer.chrome.com/docs/devtools/network/reference/#disable-cache disable the network cache].&lt;br /&gt;
* If you change an existing template or function, you won’t have to refresh the browser. In most cases, doing a PTR (Pull To Refresh) in the page that displays the template will suffice.&lt;br /&gt;
* If any of these doesn&#039;t show your changes, you may need to [https://docs.moodle.org/311/en/Developer_tools#Purge_all_caches purge all caches] to avoid problems with the auto-loading cache.&lt;br /&gt;
* Ultimately, if that doesn&#039;t work either, you may have to log out from the site and log in again. If any changes affect plugin installation, you may also need to increase the version in your plugin&#039;s &amp;lt;code&amp;gt;version.php&amp;lt;/code&amp;gt; file and upgrade it in the site.&lt;br /&gt;
==Types of plugins==&lt;br /&gt;
There are 3 types of plugins:&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
[[File:Templates_downloaded_when_requested.png|thumb]]&lt;br /&gt;
With this type of plugin, the template of your plugin will be generated and downloaded when the user opens the plugin in the app. This means that your function will receive some context parameters. For example, if you&#039;re developing a course module plugin you will receive the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; (course module ID). You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
[[File:Templates_downloaded_on_login.png|thumb]]&lt;br /&gt;
With this type of plugin, the template for your plugin will be downloaded when the user logs in into the app and will be stored in the device. This means that your function will not receive any context parameters, and you need to return a generic template that will be built with JS data like the ones in the Moodle App. When the user opens a page that includes your plugin, your template will receive the required JS data and your template will be rendered. You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
You can always implement the whole plugin yourself using JavaScript instead of using our API. In fact, this is required if you want to implement some features like capturing links in the Mobile app. You can see the list of delegates that only support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
==Step by step example==&lt;br /&gt;
In this example, we are going to update an existing plugin, the [https://github.com/mdjnelson/moodle-mod_certificate Certificate activity module], that previously used a [[Moodle Mobile 2 (Ionic 1) Remote add-ons|Remote add-on]] (a legacy approach to implement mobile plugins).&lt;br /&gt;
&lt;br /&gt;
This is a simple activity module that displays the certificate issued for the current user along with the list of the dates of previously issued certificates. It also stores in the course log that the user viewed a certificate. This module also works offline: when the user downloads the course or activity, the data is pre-fetched and can be viewed offline.&lt;br /&gt;
===Step 1. Update the &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file===&lt;br /&gt;
In this case, we are updating an existing file. For new plugins, you should create this new file.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;mod_certificate&#039; =&amp;gt; [ // Plugin identifier&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;coursecertificate&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/mod/certificate/pix/icon.gif&#039;,&lt;br /&gt;
                    &#039;class&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
       &lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseModuleDelegate&#039;, // Delegate (where to display the link to the plugin)&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;mobile_course_view&#039;, // Main function in \mod_certificate\output\mobile&lt;br /&gt;
                &#039;offlinefunctions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;mobile_course_view&#039; =&amp;gt; [],&lt;br /&gt;
                    &#039;mobile_issues_view&#039; =&amp;gt; [],&lt;br /&gt;
                ], // Function that needs to be downloaded for offline.&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [ // Language strings that are used in all the handlers.&lt;br /&gt;
            [&#039;pluginname&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;summaryofattempts&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;getcertificate&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;requiredtimenotmet&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;viewcertificateviews&#039;, &#039;certificate&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Plugin identifier:&lt;br /&gt;
: A unique name for the plugin, it can be anything (there’s no need to match the module name).&lt;br /&gt;
&lt;br /&gt;
;Handlers (Different places where the plugin will display content):&lt;br /&gt;
: A plugin can be displayed in different views in the app. Each view should have a unique name inside the plugin scope (alphanumeric).&lt;br /&gt;
&lt;br /&gt;
; Display data:&lt;br /&gt;
: This is only needed for certain types of plugins. Also, depending on the type of delegate it may require additional (or less fields). In this case, we are indicating the module icon.&lt;br /&gt;
&lt;br /&gt;
; Delegate&lt;br /&gt;
: Where to display the link to the plugin, see the [[#Delegates|Delegates]] section for all the possible options.&lt;br /&gt;
&lt;br /&gt;
; Method:&lt;br /&gt;
: This is the method in the Moodle &amp;lt;code&amp;gt;\{component-name}\output\mobile&amp;lt;/code&amp;gt; class to be executed the first time the user clicks in the new option displayed in the app.&lt;br /&gt;
&lt;br /&gt;
; Offlinefunctions&lt;br /&gt;
: This is the list of functions that need to be called and stored when the user downloads a course for offline usage. Please note that you can add functions here that are not even listed in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. &lt;br /&gt;
: In our example, downloading for offline access will mean that we&#039;ll execute the functions for getting the certificate and issued certificates passing as parameters the current &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; (and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; when we are using the mod or course delegate). If we have the result of those functions stored in the app, we&#039;ll be able to display the certificate information even if the user is offline.&lt;br /&gt;
: Offline functions will be mostly used to display information for final users, any further interaction with the view won’t be supported offline (for example, trying to send information when the user is offline).&lt;br /&gt;
: You can indicate here other Web Services functions, indicating the parameters that they might need from a defined subset (currently &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Prefetching the module will also download all the files returned by the methods in these offline functions (in the &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; array).&lt;br /&gt;
: Note that if your functions use additional custom parameters (for example, if you implement multiple pages within a module&#039;s view function by using a &amp;lt;code&amp;gt;page&amp;lt;/code&amp;gt; parameter in addition to the usual &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;) then the app will not know which additional parameters to supply. In this case, do not list the function in &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt;; instead, you will need to manually implement a [[#Module_prefetch_handler|module prefetch handler]].&lt;br /&gt;
&lt;br /&gt;
;Lang:&lt;br /&gt;
: The language pack string ids used in the plugin by all the handlers. Normally these will be strings from your own plugin, however, you can list any strings you need here, like &amp;lt;code&amp;gt;[&#039;cancel&#039;, &#039;moodle&#039;]&amp;lt;/code&amp;gt;. If you do this, be warned that in the app you will then need to refer to that string as &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.myplugin.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (not &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.moodle.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Please only include the strings you actually need. The Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform, and this will then be cached, so listing too many strings is very wasteful.&lt;br /&gt;
There are additional attributes supported by the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; list, you can find about them in the [[#Mobile.php_supported_options|Mobile.php supported options]] section.&lt;br /&gt;
===Step 2. Creating the main function===&lt;br /&gt;
The main function displays the current issued certificate (or several warnings if it’s not possible to issue a certificate). It also displays a link to view the dates of previously issued certificates.&lt;br /&gt;
&lt;br /&gt;
All the functions must be created in the plugin or subsystem &amp;lt;code&amp;gt;classes/output&amp;lt;/code&amp;gt; directory, the name of the class must be &amp;lt;code&amp;gt;mobile&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
For this example, the namespace name will be &amp;lt;code&amp;gt;mod_certificate\output&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace mod_certificate\output;&lt;br /&gt;
&lt;br /&gt;
use context_module;&lt;br /&gt;
use mod_certificate_external;&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Returns the certificate course view for the mobile app.&lt;br /&gt;
     *&lt;br /&gt;
     * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
     *&lt;br /&gt;
     * @return array       HTML, JS and other data.&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
        $args = (object) $args;&lt;br /&gt;
        $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
        // Capabilities check.&lt;br /&gt;
        require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
        $context = \context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
        require_capability(&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
        if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
            require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
        }&lt;br /&gt;
        $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
        // Get certificates from external (taking care of exceptions).&lt;br /&gt;
        try {&lt;br /&gt;
            $issued = \mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
            $certificates = \mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
            $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $issues = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Set timemodified for each certificate.&lt;br /&gt;
        foreach ($issues as $issue) {&lt;br /&gt;
            if (empty($issue-&amp;gt;timemodified)) {&lt;br /&gt;
                $issue-&amp;gt;timemodified = $issue-&amp;gt;timecreated;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $showget = true;&lt;br /&gt;
        if ($certificate-&amp;gt;requiredtime &amp;amp;&amp;amp; !has_capability(&#039;mod/certificate:manage&#039;, $context)) {&lt;br /&gt;
            if (certificate_get_course_time($certificate-&amp;gt;course) &amp;lt; ($certificate-&amp;gt;requiredtime * 60)) {&lt;br /&gt;
                $showget = false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $certificate-&amp;gt;name = format_string($certificate-&amp;gt;name);&lt;br /&gt;
        [$certificate-&amp;gt;intro, $certificate-&amp;gt;introformat] =&lt;br /&gt;
                external_format_text($certificate-&amp;gt;intro, $certificate-&amp;gt;introformat, $context-&amp;gt;id, &#039;mod_certificate&#039;, &#039;intro&#039;);&lt;br /&gt;
        $data = [&lt;br /&gt;
            &#039;certificate&#039; =&amp;gt; $certificate,&lt;br /&gt;
            &#039;showget&#039; =&amp;gt; $showget &amp;amp;&amp;amp; count($issues) &amp;gt; 0,&lt;br /&gt;
            &#039;issues&#039; =&amp;gt; $issues,&lt;br /&gt;
            &#039;issue&#039; =&amp;gt; $issues[0],&lt;br /&gt;
            &#039;numissues&#039; =&amp;gt; count($issues),&lt;br /&gt;
            &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
            &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;files&#039; =&amp;gt; $issues,&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Function declaration: &lt;br /&gt;
: The function name is the same as the one used in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file (&amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; field). There is only one argument, &amp;lt;code&amp;gt;$args&amp;lt;/code&amp;gt;, which is an array containing all the information sent by the mobile app (the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversionname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversioncode&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;applang&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appcustomurlscheme&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
&lt;br /&gt;
; Function implementation:&lt;br /&gt;
: In the first part of the function, we check permissions and capabilities (like a &amp;lt;code&amp;gt;view.php&amp;lt;/code&amp;gt; script would do normally). Then we retrieve the certificate information that’s necessary to display the template.&lt;br /&gt;
&lt;br /&gt;
; Function return:&lt;br /&gt;
* &amp;lt;code&amp;gt;templates&amp;lt;/code&amp;gt; — The rendered template (notice that we could return more than one template, but we usually would only need one). By default the app will always render the first template received, the rest of the templates can be used if the plugin defines some JavaScript code.&lt;br /&gt;
* &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; — Empty, because we don’t need any in this case.&lt;br /&gt;
* &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; — Empty as well, because we don’t need any additional data to be used by directives or components in the template. This field will be published as an object supporting 2-way data-binding in the template.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; — A list of files that the app should be able to download (for offline usage mostly).&lt;br /&gt;
===Step 3. Creating the template for the main function===&lt;br /&gt;
This is the most important part of your plugin because it contains the code that will be rendered on the mobile app.&lt;br /&gt;
&lt;br /&gt;
In this template we’ll be using Ionic, together with directives and components specific to the Moodle App.&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;ion-&amp;lt;/code&amp;gt; are ionic components. Most of the time, the component name is self-explanatory but you may refer to a detailed guide here: https://ionicframework.com/docs/components/&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;core-&amp;lt;/code&amp;gt; are custom components of the Moodle App.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_page.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;core-course-module-description description=&amp;quot;&amp;lt;% certificate.intro %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-course-module-description&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;ion-list-header&amp;gt;&lt;br /&gt;
            &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.summaryofattempts&#039; | translate }}&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-list-header&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-new-content title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; &lt;br /&gt;
                        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
                        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
                    {{ &#039;plugin.mod_certificate.viewcertificateviews&#039; | translate: {$a: &amp;lt;% numissues %&amp;gt;} }}&lt;br /&gt;
                &amp;lt;/ion-button&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&lt;br /&gt;
                        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
                        [files]=&amp;quot;[{&lt;br /&gt;
                            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
                            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
                            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
                        }]&amp;quot;&amp;gt;&lt;br /&gt;
                 &lt;br /&gt;
                        &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot; slot=&amp;quot;start&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                            {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
                    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%^showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;p&amp;gt;{{ &#039;plugin.mod_certificate.requiredtimenotmet&#039; | translate }}&amp;lt;/p&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;!-- Call log WS when the template is loaded. --&amp;gt;&lt;br /&gt;
        &amp;lt;span core-site-plugins-call-ws-on-load name=&amp;quot;mod_certificate_view_certificate&amp;quot;&lt;br /&gt;
                [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/span&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache).&lt;br /&gt;
&lt;br /&gt;
Then we display the module description using &amp;lt;code&amp;gt;core-course-module-description&amp;lt;/code&amp;gt;, which is a component used to include the course module description.&lt;br /&gt;
&lt;br /&gt;
For displaying the certificate information we create a list of elements, adding a header on top.&lt;br /&gt;
&lt;br /&gt;
The following line using the &amp;lt;code&amp;gt;translate&amp;lt;/code&amp;gt; filter indicates that the app will translate the &amp;lt;code&amp;gt;summaryofattempts&amp;lt;/code&amp;gt; string id (here we could’ve used mustache translation but it is usually better to delegate the strings translations to the app). The string id has the following format: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
plugin.{plugin-identifier}.{string-id}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Where &amp;lt;code&amp;gt;{plugin-identifier}&amp;lt;/code&amp;gt; is taken from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;{string-id}&amp;lt;/code&amp;gt; must be indicated in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; field in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Then, we display a button to transition to another page if there are certificates issued. The attribute (directive) &amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt; indicates that if the user clicks the button, we need to call the &amp;lt;code&amp;gt;mobile_issues_view&amp;lt;/code&amp;gt; function in the &amp;lt;code&amp;gt;mod_certificate&amp;lt;/code&amp;gt; component; passing as arguments the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;. The content returned by this function will be displayed in a new page (read the following section to see the code of this new page).&lt;br /&gt;
&lt;br /&gt;
Just after this button, we display another one but this time for downloading an issued certificate. The &amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt; directive indicates that clicking this button is for downloading the whole activity and opening the main file. This means that, when the user clicks this button, the whole certificate activity will be available offline.&lt;br /&gt;
&lt;br /&gt;
Finally, just before the &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt; is closed, we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt; directive to indicate that once the page is loaded, we need to call a Web Service function in the server, in this case we are calling the &amp;lt;code&amp;gt;mod_certificate_view_certificate&amp;lt;/code&amp;gt; that will log that the user viewed this page.&lt;br /&gt;
&lt;br /&gt;
As you can see, no JavaScript was necessary at all. We used plain HTML elements and attributes that did all the complex dynamic logic (like calling a Web Service) behind the scenes.&lt;br /&gt;
&lt;br /&gt;
===Step 4. Adding an additional page===&lt;br /&gt;
Add the following method to &amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Returns the certificate issues view for the mobile app.&lt;br /&gt;
 * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
 *&lt;br /&gt;
 * @return array       HTML, JS and other data.&lt;br /&gt;
 */&lt;br /&gt;
public static function mobile_issues_view($args) {&lt;br /&gt;
    global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
    $args = (object) $args;&lt;br /&gt;
    $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
    // Capabilities check.&lt;br /&gt;
    require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
    $context = context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
    require_capability (&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
    if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
        require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
    }&lt;br /&gt;
    $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
    // Get certificates from external (taking care of exceptions).&lt;br /&gt;
    try {&lt;br /&gt;
        $issued = mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
        $certificates = mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
        $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
    } catch (Exception $e) {&lt;br /&gt;
        $issues = [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $data = [&#039;issues&#039; =&amp;gt; $issues];&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
            [&lt;br /&gt;
                &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_issues&#039;, $data),&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
        &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This method for the new page was added just after &amp;lt;code&amp;gt;mobile_course_view&amp;lt;/code&amp;gt;, the code is quite similar: checks the capabilities, retrieves the information required for the template, and returns the template rendered.&lt;br /&gt;
&lt;br /&gt;
The code of the mustache template is also very simple.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_issues.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &amp;lt;%timecreated%&amp;gt; | coreToLocaleString }}&amp;lt;/p&amp;gt;&lt;br /&gt;
                    &amp;lt;p&amp;gt;&amp;lt;%grade%&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As we did in the previous template, in the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache). &lt;br /&gt;
&lt;br /&gt;
Here we are creating an Ionic list that will display a new item in the list per each issued certificated.&lt;br /&gt;
&lt;br /&gt;
For the issued certificated we’ll display the time when it was created (using the app filter &amp;lt;code&amp;gt;coreToLocaleString&amp;lt;/code&amp;gt;). We are also displaying the grade displayed in the certificate (if any).&lt;br /&gt;
&lt;br /&gt;
===Step 5. Plugin webservices, if included===&lt;br /&gt;
If your plugin uses its own web services, they will also need to be enabled for mobile access in your &amp;lt;code&amp;gt;db/services.php&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
The following line should be included in each webservice definition:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;services&#039; =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/db/services.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$functions = [&lt;br /&gt;
    &#039;mod_certificate_get_certificates_by_courses&#039; =&amp;gt; [&lt;br /&gt;
        &#039;classname&#039;     =&amp;gt; &#039;mod_certificate_external&#039;,&lt;br /&gt;
        &#039;methodname&#039;    =&amp;gt; &#039;get_certificates_by_courses&#039;,&lt;br /&gt;
        &#039;description&#039;   =&amp;gt; &#039;Returns a list of certificate instances...&#039;,&lt;br /&gt;
        &#039;type&#039;          =&amp;gt; &#039;read&#039;,&lt;br /&gt;
        &#039;capabilities&#039;  =&amp;gt; &#039;mod/certificate:view&#039;,&lt;br /&gt;
        &#039;services&#039;      =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Mobile.php supported options==&lt;br /&gt;
In the previous section, we learned about some of the existing options for handlers configuration. This is the full list of supported options.&lt;br /&gt;
===Common options===&lt;br /&gt;
* &amp;lt;code&amp;gt;delegate&amp;lt;/code&amp;gt; (mandatory) — Name of the delegate to register the handler in.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (mandatory) — The method to call to retrieve the main page content.&lt;br /&gt;
* &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; (optional) — A method to call to retrieve the initialisation JS and the restrictions to apply to the whole handler. It can also return templates that can be used from JavaScript. You can learn more about this in the [[#Initialisation|Initialisation]] section.&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttocurrentuser&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForUser&amp;lt;/code&amp;gt; function. If true, the handler will only be shown for the current user. For more info about displaying the plugin only for certain users, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttoenrolledcourses&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForCourse&amp;lt;/code&amp;gt; function. If true or not defined, the handler will only be shown for courses the user is enrolled in. For more info about displaying the plugin only for certain courses, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;styles&amp;lt;/code&amp;gt; (optional) — An array with two properties: &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt;. The URL should point to a CSS file, either using an absolute URL or a relative URL. This file will be downloaded and applied by the app. It&#039;s recommended to include styles that will only affect your plugin templates. The version number is used to determine if the file needs to be downloaded again, you should change the version number everytime you change the CSS file.&lt;br /&gt;
* &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; (optional) — If your plugin supports a component in the app different than the one defined by your plugin, you can use this property to specify it. For example, you can create a local plugin to support a certain course format, activity, etc. The component of your plugin in Moodle would be &amp;lt;code&amp;gt;local_whatever&amp;lt;/code&amp;gt;, but in &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; you can specify that this handler will implement &amp;lt;code&amp;gt;format_whatever&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;mod_whatever&amp;lt;/code&amp;gt;. This property was introduced in the version 3.6.1 of the app.&lt;br /&gt;
===Options only for CoreMainMenuDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. Main Menu plugins are always displayed in the More tab, they cannot be displayed as tabs in the bottom bar.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreMainMenuHomeDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseOptionsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ismenuhandler&amp;lt;/code&amp;gt; (optional) — Supported from the 3.7.1 version of the app. Set it to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if you want your plugin to be displayed in the contextual menu of the course instead of in the top tabs. The contextual menu is displayed when you click in the 3-dots button at the top right of the course.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseModuleDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — Path to the module icon. After Moodle app 4.0, this icon is only used as a fallback, the app will always try to use the theme icon so themes can override icons in the app.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (optional) — The function to call to retrieve the main page content. In this delegate the method is optional. If the method is not set, the module won&#039;t be clickable.&lt;br /&gt;
* &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt; (optional) — List of functions to call when prefetching the module. It can be a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; method or a WS. You can filter the params received by the WS. By default, WS will receive these params: &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;. Other valid values that will be added if they are present in the list of params: &amp;lt;code&amp;gt;courseids&amp;lt;/code&amp;gt; (it will receive a list with the courses the user is enrolled in), &amp;lt;code&amp;gt;{component}id&amp;lt;/code&amp;gt; (For example, &amp;lt;code&amp;gt;certificateid&amp;lt;/code&amp;gt;).&lt;br /&gt;
* &amp;lt;code&amp;gt;downloadbutton&amp;lt;/code&amp;gt; (optional) — Whether to display download button in the module. If not defined, the button will be shown if there is any offlinefunction.&lt;br /&gt;
* &amp;lt;code&amp;gt;isresource&amp;lt;/code&amp;gt; (optional) — Whether the module is a resource or an activity. Only used if there is any offline function. If your module relies on the &amp;lt;code&amp;gt;contents&amp;lt;/code&amp;gt; field, then it should be &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;updatesnames&amp;lt;/code&amp;gt; (optional) — Only used if there is any offline function. A regular expression to check if there&#039;s any update in the module. It will be compared to the result of &amp;lt;code&amp;gt;core_course_check_updates&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayopeninbrowser&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Open in browser&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayOpenInBrowser = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydescription&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Description&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayDescription = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayrefresh&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Refresh&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayRefresh = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayprefetch&amp;lt;/code&amp;gt; (optional) — Whether the module should display the download option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayPrefetch = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysize&amp;lt;/code&amp;gt; (optional) — Whether the module should display the downloaded size in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displaySize = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; (optional) — It can be used to specify the supported features of the plugin. Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. It should be an array with features as keys (For example, &amp;lt;code&amp;gt;[FEATURE_NO_VIEW_LINK =&amp;gt; true&amp;lt;/code&amp;gt;). If you need to calculate this dynamically please see [[#Module_plugins:_dynamically_determine_if_a_feature_is_supported|Module plugins: dynamically determine if a feature is supported]]. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; (optional) — If set, this method will be called when the course is rendered and the HTML returned will be displayed in the course page for the module. Please notice the HTML returned should not contain directives or components, only default HTML. Supported from the 3.8 version of the app.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreCourseFormatDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;canviewallsections&amp;lt;/code&amp;gt; (optional) — Whether the course format allows seeing all sections in a single page. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayenabledownload&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, it&#039;s no longer used.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysectionselector&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, use &#039;&#039;displaycourseindex&#039;&#039; instead.&lt;br /&gt;
*&amp;lt;code&amp;gt;displaycourseindex&amp;lt;/code&amp;gt; (optional) — Whether the default course index should be displayed. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
===Options only for CoreUserDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — The type of the addon. The values accepted are &amp;lt;code&amp;gt;&#039;newpage&#039;&amp;lt;/code&amp;gt; (default) and &amp;lt;code&amp;gt;&#039;communication&#039;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreSettingsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for AddonMessageOutputDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreBlockDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (optional):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section. If this is not supplied, it will default to &amp;lt;code&amp;gt;&#039;plugins.block_{block-name}.pluginname&#039;&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class. If this is not supplied, it will default to &amp;lt;code&amp;gt;block_{block-name}&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — Possible values are:&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;title&amp;quot;&amp;lt;/code&amp;gt; — Your block will only display the block title, and when it&#039;s clicked it will open a new page to display the block contents (the template returned by the block&#039;s method).&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;prerendered&amp;quot;&amp;lt;/code&amp;gt; — Your block will display the content and footer returned by the WebService to get the blocks (for example, &amp;lt;code&amp;gt;core_block_get_course_blocks&amp;lt;/code&amp;gt;), so your block&#039;s method will never be called.&lt;br /&gt;
*** Any other value — Your block will immediately call the method specified in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and it will use the template to render the block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fallback&amp;lt;/code&amp;gt; (optional) — This option allows you to specify a block to use in the app instead of your block. For example, you can make the app display the &amp;quot;My overview&amp;quot; block instead of your block in the app by setting &amp;lt;code&amp;gt;&#039;fallback&#039; =&amp;gt; &#039;myoverview&#039;&amp;lt;/code&amp;gt;. The fallback will only be used if you don&#039;t specify a &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; is different to &amp;lt;code&amp;gt;&#039;title&#039;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&#039;prerendered&#039;&amp;lt;/code&amp;gt;. Supported from the 3.9.0 version of the app.&lt;br /&gt;
==Delegates==&lt;br /&gt;
Delegates can be classified by type of plugin. For more info about type of plugins, please see the [[#Types_of_plugins|Types of plugins]] section.&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new items to the main menu (currently displayed at the bottom of the app). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuHomeDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new tabs in the home page (by default the app is displaying the &amp;quot;Dashboard&amp;quot; and &amp;quot;Site home&amp;quot; tabs). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new options in a course (Participants or Grades are examples of this type of delegate).&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting activity modules or resources.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add additional options in the user profile page in the app.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting course formats. When you open a course from the course list in the mobile app, it will check if there is a &amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt; handler for the format that site uses. If so, it will display the course using that handler. Otherwise, it will use the default app course format.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile course formats]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreSettingsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to add a new option in the settings page.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonMessageOutputDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a message output plugin.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreBlockDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a block. For example, blocks can be displayed in Site Home, Dashboard and the Course page.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question types.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile question types]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionBehaviourDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question behaviours.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting user profile fields.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModQuizAccessRuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a quiz access rule.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModAssignSubmissionDelegate&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;AddonModAssignFeedbackDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use these delegates to support assign submission or feedback plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonWorkshopAssessmentStrategyDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a workshop assessment strategy plugin.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
These delegates require JavaScript to be supported. See [[#Initialisation|Initialisation]] for more information.&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreContentLinksDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreCourseModulePrefetchDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFileUploaderDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CorePluginFileDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFilterDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
==Available components and directives==&lt;br /&gt;
===Difference between components and directives===&lt;br /&gt;
A directive is usually represented as an HTML attribute, allows you to extend a piece of HTML with additional information or functionality. Example of directives are: &amp;lt;code&amp;gt;core-auto-focus&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*ngIf&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ng-repeat&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components are also directives, but they are usually represented as an HTML tag and they are used to add custom elements to the app. Example of components are &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-item&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;core-search-box&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components and directives are Angular concepts; you can learn more about them and the components come out of the box with Ionic in the following links:&lt;br /&gt;
* [https://angular.io/guide/built-in-directives Angular directives documentation]&lt;br /&gt;
* [https://ionicframework.com/docs/components Ionic components]&lt;br /&gt;
===Custom core components and directives===&lt;br /&gt;
These are some useful custom components and directives that are only available in the Moodle App. Please note that this isn’t the full list of custom components and directives, it’s just an extract of the most common ones.&lt;br /&gt;
&lt;br /&gt;
You can find a full list of components and directives in the source code of the app, within [https://github.com/moodlehq/moodleapp/tree/master/src/core/components &amp;lt;code&amp;gt;src/core/components&amp;lt;/code&amp;gt;] and [https://github.com/moodlehq/moodleapp/tree/master/src/core/directives &amp;lt;code&amp;gt;src/core/directives&amp;lt;/code&amp;gt;].&lt;br /&gt;
====&amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;====&lt;br /&gt;
This directive formats the text and adds some directives needed for the app to work as it should. For example, it treats all links and all the embedded media so they work fine in the app. If some content in your template includes links or embedded media, please use this directive.&lt;br /&gt;
&lt;br /&gt;
This directive automatically applies &amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt; to all the links and embedded media.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;text&amp;lt;/code&amp;gt; (string) — The text to format.&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;adaptImg&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to adapt images to screen width. &lt;br /&gt;
* &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether all HTML tags should be removed.&lt;br /&gt;
* &amp;lt;code&amp;gt;singleLine&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether new lines should be removed to display all the text in single line. Only if &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; (number) — Optional. Max height in pixels to render the content box. The minimum accepted value is 50. Using this parameter will force &amp;lt;code&amp;gt;display: block&amp;lt;/code&amp;gt; to calculate the height better. If you want to avoid this, use &amp;lt;code&amp;gt;class=&amp;quot;inline&amp;quot;&amp;lt;/code&amp;gt; at the same time to use &amp;lt;code&amp;gt;display: inline-block&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;fullOnClick&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether it should open a new page with the full contents on click. Only if &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; is set and the content has been collapsed. &lt;br /&gt;
* &amp;lt;code&amp;gt;fullTitle&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;Description&amp;quot;&amp;lt;/code&amp;gt;. Title to use in full view.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-format-text text=&amp;quot;&amp;lt;% cm.description %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle a link. It performs several checks, like checking if the link needs to be opened in the app, and opens the link as it should (without overriding the app).&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;capture&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether the link needs to be captured by the app (check if the link can be handled by the app instead of opening it in a browser).&lt;br /&gt;
* &amp;lt;code&amp;gt;inApp&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to open in an embedded browser within the app or in the system browser.&lt;br /&gt;
* &amp;lt;code&amp;gt;autoLogin&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt;. If the link should be open with auto-login. Accepts the following values:&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;yes&amp;quot;&amp;lt;/code&amp;gt; — Always auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;no&amp;quot;&amp;lt;/code&amp;gt; — Never auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt; — Auto-login only if it points to the current site.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a href=&amp;quot;&amp;lt;% cm.url %&amp;gt;&amp;quot; core-link&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle links to files and embedded files. This directive should be used in any link to a file or any embedded file that you want to have available when the app is offline. &lt;br /&gt;
&lt;br /&gt;
If a file is downloaded, its URL will be replaced by the local file URL.&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;&amp;lt;% event.iconurl %&amp;gt;&amp;quot; core-external-content component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% event.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-user-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to go to user profile on click. When the user clicks the element where this directive is attached, the right user profile will be opened.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;userId&amp;lt;/code&amp;gt; (number) — User id to open the profile.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — Optional. Course id to show the user info related to that course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a ion-item core-user-link userId=&amp;quot;&amp;lt;% userid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to handle a remote file. It shows the file name, icon (depending on mime type) and a button to download or refresh it. The user can identify if the file is downloaded or not based on the button.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;file&amp;lt;/code&amp;gt; (object) — The file. Must have a &amp;lt;code&amp;gt;filename&amp;lt;/code&amp;gt; property and either &amp;lt;code&amp;gt;fileurl&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component the file belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDelete&amp;lt;/code&amp;gt; (boolean) — Optional. Whether the file can be deleted.&lt;br /&gt;
* &amp;lt;code&amp;gt;alwaysDownload&amp;lt;/code&amp;gt; (boolean) — Optional. Whether it should always display the refresh button when the file is downloaded. Use it for files that you cannot determine if they&#039;re outdated or not.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDownload&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether file can be downloaded. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-file&lt;br /&gt;
        [file]=&amp;quot;{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.url %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.name %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            filesize: &#039;&amp;lt;% issue.size %&amp;gt;&#039;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/core-file&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening a file. When the item with this directive is clicked, the file will be downloaded (if needed) and opened.&lt;br /&gt;
&lt;br /&gt;
It is usually recommended to use the &amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt; component since it also displays the state of the file.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt; (object) — The file to download.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. Component ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage (a button to download a file):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button&lt;br /&gt;
        [core-download-file]=&amp;quot;{&lt;br /&gt;
            fileurl: &amp;lt;% issue.url %&amp;gt;,&lt;br /&gt;
            timemodified: &amp;lt;% issue.timemodified %&amp;gt;,&lt;br /&gt;
            filesize: &amp;lt;% issue.size %&amp;gt;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.download | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening the main file of a module.&lt;br /&gt;
&lt;br /&gt;
When the item with this directive is clicked, the whole module will be downloaded (if needed) and its main file opened. This is meant for modules like &amp;lt;code&amp;gt;mod_resource&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This directive must receive either a &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; or a &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. If no files are provided, it will use &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; (object) — Optional, required if module is not supplied. The module object.&lt;br /&gt;
* &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt; (number) — Optional, required if module is not supplied. The module ID.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — The course ID the module belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional, defaults to the same value as &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. Component ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; (object[]) — Optional. List of files of the module. If not provided, uses &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot; &lt;br /&gt;
        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        [files]=&amp;quot;[{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
        }]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-navbar-buttons&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to add buttons to the app&#039;s header without having to place them inside the header itself. Using this component in a site plugin will allow adding buttons to the header of the current page.&lt;br /&gt;
&lt;br /&gt;
If this component indicates a position (start/end), the buttons will only be added if the header has some buttons in that position. If no start/end is specified, then the buttons will be added to the first &amp;lt;code&amp;gt;&amp;lt;ion-buttons&amp;gt;&amp;lt;/code&amp;gt; found in the header.&lt;br /&gt;
&lt;br /&gt;
You can use the &amp;lt;code&amp;gt;[hidden]&amp;lt;/code&amp;gt; input to hide all the inner buttons if a certain condition is met.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons end&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button (click)=&amp;quot;action()&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-icon slot=&amp;quot;icon-only&amp;quot; name=&amp;quot;funnel&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can also use this to add options to the context menu, for example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons&amp;gt;&lt;br /&gt;
    &amp;lt;core-context-menu&amp;gt;&lt;br /&gt;
        &amp;lt;core-context-menu-item&lt;br /&gt;
                [priority]=&amp;quot;500&amp;quot; content=&amp;quot;Nice boat&amp;quot; (action)=&amp;quot;boatFunction()&amp;quot;&lt;br /&gt;
                iconAction=&amp;quot;boat&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/core-context-menu-item&amp;gt;&lt;br /&gt;
    &amp;lt;/core-context-menu&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Using &#039;font&#039; icons with &amp;lt;code&amp;gt;ion-icon&amp;lt;/code&amp;gt;====&lt;br /&gt;
&lt;br /&gt;
Font icons are widely used on the app and Moodle LMS website. In order to support [https://fontawesome.com/v5/search?m=free font awesome icons]. We&#039;ve added a directive that uses prefixes on the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; attribute to use different font icons.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fas-&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;fa-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=solid Font awesome solid] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;far-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=regular Font awesome regular] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fab-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=brands Font awesome brands] library (But only a few are supported and we discourage to use them).&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;moodle-&amp;lt;/code&amp;gt; will use some svg icons [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/moodle imported from Moodle LMS].&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fam-&amp;lt;/code&amp;gt; will use [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/font-awesome customized font awesome icons].&lt;br /&gt;
* If the prefix is not found or not valid, the app will search the icon name on the [https://ionic.io/ionicons ionicons library].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example of usage to show icon &amp;quot;pizza-slice&amp;quot; from font-awesome regular library:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-icon name=&amp;quot;fas-pizza-slice&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We encourage the use of font-awesome icons to match the appearance from the LMS website version.&lt;br /&gt;
&lt;br /&gt;
===Specific component and directives for plugins===&lt;br /&gt;
These are component and directives created specifically for supporting Moodle plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to display a new content when clicked. This new content can be displayed in a new page or in the current page (only if the current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The params to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in v3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in same page or open a new one. &lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that doing &amp;lt;code&amp;gt;[useOtherData]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to go to a new content page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content &lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; &lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to load new content in current page using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot; samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked. The action to do when the WS call is successful depends on the provided data: display a message, go back or refresh current view.&lt;br /&gt;
&lt;br /&gt;
If you want to load a new content when the WS call is done, please see [[#core-site-plugins-call-ws-new-content|&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The params for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;successMessage&amp;lt;/code&amp;gt; (string) — Message to show on success. If not supplied, no message. If supplied but empty, defaults to &amp;quot;Success&amp;quot;.&lt;br /&gt;
* &amp;lt;code&amp;gt;goBackOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to go back if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to refresh the current view if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to send some data to the server without using cache, displaying default messages and refreshing on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage successMessage&lt;br /&gt;
        refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to send some data to the server using cache without confirming, going back on success and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        goBackOnSuccess=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Same as the previous example, but implementing custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;certificateViewed($event)&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.certificateViewed = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked and load a new content passing the WS result as arguments. This new content can be displayed in a new page or in the same page (only if current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to load some new content when done, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The parameters to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in the same page or open a new one.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. The format is the same as in &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;jsData&amp;lt;/code&amp;gt; (any) — JS variables to pass to the new page so they can be used in the template or JS. If &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; is supplied instead of an object, all initial variables from current page will be copied. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;newContentPreSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in 3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to get some data from the server without using cache, showing default confirm and displaying a new page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage&lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to get some data from the server using cache, without confirm, displaying new content in same page and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Same as the previous example, but implementing a custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS as soon as the template is loaded. This directive is meant for actions to do in the background, like calling logging Web Services.&lt;br /&gt;
&lt;br /&gt;
If you want to call a WS when the user clicks on a certain element, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span core-site-plugins-call-ws-on-load&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Advanced features==&lt;br /&gt;
===Display the plugin only if certain conditions are met===&lt;br /&gt;
You might want to display your plugin in the mobile app only if certain dynamic conditions are met, so the plugin would be displayed only for some users. This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section ahead).&lt;br /&gt;
&lt;br /&gt;
All initialisation methods are called as soon as your plugin is retrieved. If you don&#039;t want your plugin to be displayed for the current user, then you should return the following in the initialisation method (only for Moodle site 3.8 and onwards):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;disabled&#039; =&amp;gt; true];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If the Moodle site is older than 3.8, then the initialisation method should return this instead:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;javascript&#039; =&amp;gt; &#039;this.HANDLER_DISABLED&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
On the other hand, you might want to display a plugin only for certain courses (&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;) or only if the user is viewing certain users&#039; profiles (&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;). This can be achieved with the initialisation method too.&lt;br /&gt;
&lt;br /&gt;
In the initialisation method you can return a &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; property with two fields in it: &amp;lt;code&amp;gt;courses&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;users&amp;lt;/code&amp;gt;. If you return a list of courses IDs in this property, then your plugin will only be displayed when the user views any of those courses. In the same way, if you return a list of user IDs then your plugin will only be displayed when the user views any of those users&#039; profiles.&lt;br /&gt;
===Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;===&lt;br /&gt;
The values returned by the functions in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; are added to a variable so they can be used both in JavaScript and in templates. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call is added to a variable named &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt;, while the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call is added to a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call will be passed to the JS and template of all the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; calls in that handler. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call will only be passed to the JS and template returned by that &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call.&lt;br /&gt;
&lt;br /&gt;
This means that, in your JavaScript, you can access and use the data like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.CONTENT_OTHERDATA.myVar;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the template you could use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
{{ CONTENT_OTHERDATA.myVar }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; is the name we put to one of our variables, it can be any name that you want. In the example above, this is the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
[&#039;myVar&#039; =&amp;gt; &#039;Initial value&#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Example====&lt;br /&gt;
In our plugin, we want to display an input text with a certain initial value. When the user clicks a button, we want the value in the input to be sent to a certain Web Service. This can be done using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We will return the initial value of the input in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; of our PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;myVar&#039; =&amp;gt; &#039;My initial value&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then in the template we will use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item text-wrap&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label position=&amp;quot;stacked&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.textlabel | translate }}&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;text&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.myVar&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws name=&amp;quot;mod_certificate_my_webservice&amp;quot; [useOtherDataForWS]=&amp;quot;[&#039;myVar&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we are creating an input text and we use &amp;lt;code&amp;gt;[(ngModel)]&amp;lt;/code&amp;gt; to use the value in &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; as the initial value and to store the changes in the same &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This means that the initial value of the input will be &amp;quot;My initial value&amp;quot;, and if the user changes the value of the input these changes will be applied to the &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This is called 2-way data binding in Angular.&lt;br /&gt;
&lt;br /&gt;
Then we add a button to send this data to a WS, and for that we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive. We use the &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; attribute to specify which variable from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; we want to send to our WebService. So if the user enters &amp;quot;A new value&amp;quot; in the input and then clicks the button, it will call the WebService &amp;lt;code&amp;gt;mod_certificate_my_webservice&amp;lt;/code&amp;gt; and will send as a parameter &amp;lt;code&amp;gt;[&#039;myVar&#039; =&amp;gt; &#039;A new value&#039;]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We can also achieve the same result using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; attribute of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive instead of using &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws &lt;br /&gt;
        name=&amp;quot;mod_certificate_my_webservice&amp;quot; [params]=&amp;quot;{myVar: CONTENT_OTHERDATA.myVar}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The Web Service call will be exactly the same with both versions.&lt;br /&gt;
&lt;br /&gt;
Notice that this example could be done without using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; too, using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; input of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive.&lt;br /&gt;
===Running JS code after a content template has loaded===&lt;br /&gt;
When you return JavaScript code from a handler function using the &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; array key, this code is executed immediately after the web service call returns, which may be before the returned template has been rendered into the DOM. &lt;br /&gt;
&lt;br /&gt;
If your code needs to run after the DOM has been updated, you can use &amp;lt;code&amp;gt;setTimeout&amp;lt;/code&amp;gt; to call it. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&lt;br /&gt;
    &#039;template&#039; =&amp;gt; [&lt;br /&gt;
        // ...&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;javascript&#039; =&amp;gt; &#039;setTimeout(function() { console.log(&amp;quot;DOM is available now&amp;quot;); });&#039;,&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    &#039;files&#039; =&amp;gt; [],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice that if you wanted to write a lot of code here, you might be better off putting it in a function defined in the response from an initialisation template, so that it does not get loaded again with each page of content.&lt;br /&gt;
===JS functions visible in the templates===&lt;br /&gt;
The app provides some JavaScript functions that can be used from the templates to update, refresh or view content. These are the functions:&lt;br /&gt;
* &amp;lt;code&amp;gt;openContent(title: string, args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Open a new page to display some new content. You need to specify the &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; of the new page and the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshContent(showSpinner = true)&amp;lt;/code&amp;gt; — Refresh the current content. By default, it will display a spinner while refreshing. If you don&#039;t want it to be displayed, you should pass &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; as a parameter.&lt;br /&gt;
* &amp;lt;code&amp;gt;updateContent(args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Refresh the current content using different parameters. You need to specify the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Group selector=====&lt;br /&gt;
Imagine we have an activity that uses groups and we want to let the user select which group they want to see. A possible solution would be to return all the groups in the same template (hidden), and then show the group user selects. However, we can make it more dynamic and return only the group the user is requesting.&lt;br /&gt;
&lt;br /&gt;
To do so, we&#039;ll use a drop down to select the group. When the user selects a group using this drop down, we&#039;ll update the page content to display the new group.&lt;br /&gt;
&lt;br /&gt;
The main difficulty in this is to tell the view which group needs to be selected when the view is loaded. There are 2 ways to do it: using plain HTML or using Angular&#039;s &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;.&lt;br /&gt;
======Using plain HTML======&lt;br /&gt;
We need to add a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; attribute to the option that needs to be selected. To do so, we need to pre-caclulate the selected option in the PHP code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// Detect which group is selected.&lt;br /&gt;
foreach ($groups as $gid=&amp;gt;$group) {&lt;br /&gt;
    $group-&amp;gt;selected = $gid === $groupid;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$data = [&lt;br /&gt;
    &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
    &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
    &#039;groups&#039; =&amp;gt; $groups,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the code above, we&#039;re retrieving the groups the user can see and then we&#039;re adding a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; boolean to each one to determine which one needs to be selected in the drop down. Finally, we pass the list of groups to the template.&lt;br /&gt;
&lt;br /&gt;
In the template, we display the drop down like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: $event})&amp;quot; interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot; &amp;lt;%#selected%&amp;gt;selected&amp;lt;%/selected%&amp;gt; &amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The &amp;lt;code&amp;gt;ionChange&amp;lt;/code&amp;gt; function will be called every time the user selects a different group with the drop down. We&#039;re using the &amp;lt;code&amp;gt;updateContent&amp;lt;/code&amp;gt; function to update the current view using the new group. &amp;lt;code&amp;gt;$event&amp;lt;/code&amp;gt; is an Angular variable that will have the selected value (in our case, the group ID that was just selected). This is enough to make the group selector work.&lt;br /&gt;
======Using &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;======&lt;br /&gt;
&amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; is an Angular directive that allows storing the value of a certain input or select in a JavaScript variable, and also the opposite way: tell the input or select which value to set. The main problem is that we cannot initialise a JavaScript variable from the template, so we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the PHP function we&#039;ll return the group that needs to be selected in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
        &#039;group&#039; =&amp;gt; $groupid,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we don&#039;t need to iterate over the groups array like in the plain HTML example. However, now we&#039;re returning the group id in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array. As it&#039;s explained in the [[#Using_otherdata|Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;]] section, this &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; is visible in the templates inside a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. So in the template we&#039;ll use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.group&amp;quot;&lt;br /&gt;
        (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: CONTENT_OTHERDATA.group})&amp;quot;&lt;br /&gt;
        interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Use the rich text editor===&lt;br /&gt;
The rich text editor included in the app requires a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt; to work. You can use the &amp;lt;code&amp;gt;FormBuilder&amp;lt;/code&amp;gt; library to create this control (or to create a whole &amp;lt;cide&amp;gt;FormGroup if you prefer).&lt;br /&gt;
&lt;br /&gt;
With the following JavaScript you&#039;ll be able to create a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.control = this.FormBuilder.control(this.CONTENT_OTHERDATA.rte);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we&#039;re using a value returned in &amp;lt;code&amp;gt;OTHERDATA&amp;lt;/code&amp;gt; as the initial value of the rich text editor, but you can use whatever you want.&lt;br /&gt;
&lt;br /&gt;
Then you need to pass this control to the rich text editor in your template:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;core-rich-text-editor item-content [control]=&amp;quot;control&amp;quot; placeholder=&amp;quot;Enter your text here&amp;quot; name=&amp;quot;rte_answer&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-rich-text-editor&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, there are several ways to send the value in the rich text editor to a Web Service to save it. This is one of the simplest options:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_webservice&amp;quot; [params]=&amp;quot;{rte: control.value}&amp;quot; ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re passing the value of the rich text editor as a parameter to our Web Service.&lt;br /&gt;
===Initialisation===&lt;br /&gt;
All handlers can specify an &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; method in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. This method is meant to return some JavaScript code that needs to be executed as soon as the plugin is retrieved.&lt;br /&gt;
&lt;br /&gt;
When the app retrieves all the handlers, the first thing it will do is call the &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt; Web Service with the initialisation method. This WS call will only receive the default arguments.&lt;br /&gt;
&lt;br /&gt;
The app will immediately execute the JavaScript code returned by this WS call. This JavaScript can be used to manually register your handlers in the delegates you want, without having to rely on the default handlers built based on the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; data.&lt;br /&gt;
&lt;br /&gt;
The templates returned by this method will be added to a &amp;lt;code&amp;gt;INIT_TEMPLATES&amp;lt;/code&amp;gt; variable that will be passed to all the JavaScript code of that handler. This means that the JavaScript returned by the initialisation method or the main method can access any of the templates HTML like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.INIT_TEMPLATES[&#039;main&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In this case, &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is the ID of the template we want to use.&lt;br /&gt;
&lt;br /&gt;
The same happens with the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the initialisation method, it is added to an &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt; variable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; field returned by this call will be used to determine if your handler is enabled or not. For example, if your handler is for the delegate &amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt; and you return a list of course ids in &amp;lt;code&amp;gt;restrict.courses&amp;lt;/code&amp;gt;, then your handler will only be enabled in the courses you returned. This only applies to the default handlers, if you register your own handler using the JavaScript code then you should check yourself if the handler is enabled.&lt;br /&gt;
&lt;br /&gt;
Finally, if you return an object in this initialisation JavaScript code, all the properties of that object will be passed to all the JavaScript code of that handler so you can use them when the code is run. For example, if your JavaScript code does something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    MyAddonClass: new MyAddonClass()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then, for the rest of JavaScript code of your handler (for example, the main method) you can use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.MyAddonClass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Link handlers=====&lt;br /&gt;
A link handler allows you to decide what to do when a link with a certain URL is clicked. This is useful, for example, to open your plugin page when a link to your plugin is clicked.&lt;br /&gt;
&lt;br /&gt;
After the 4.0 version, the Moodle app automatically creates two link handlers for module plugins, you don&#039;t need to create them in your plugin&#039;s Javascript code anymore:&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginanme/view.php?id=X&#039;&#039;. When this link is clicked, it will open your module in the app.&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginname/index.php?id=X&#039;&#039;. When this link is clicked, it will open a page in the app listing all the modules of your type inside a certain course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Link handlers have some advanced features that allow you to change how links behave under different conditions.&lt;br /&gt;
======Patterns======&lt;br /&gt;
You can define a Regular Expression pattern to match certain links. This will apply the handler only to links that match the pattern.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = RegExp(&#039;\/mod\/foo\/specialpage.php&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Priority======&lt;br /&gt;
Multiple link handlers may apply to a given link. You can define the order of precedence by setting the priority; the handler with the highest priority will be used.&lt;br /&gt;
&lt;br /&gt;
All default handlers have a priority of 0, so 1 or higher will override the default.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Multiple actions======&lt;br /&gt;
Once a link has been matched, the handler&#039;s &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method determines what the link should do. This method has access to the URL and its parameters.&lt;br /&gt;
&lt;br /&gt;
Different actions can be returned depending on different conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                action: function(siteId, navCtrl) {&lt;br /&gt;
                    // The actual behaviour of the link goes here.&lt;br /&gt;
                },&lt;br /&gt;
                sites: [&lt;br /&gt;
                    // ...&lt;br /&gt;
                ],&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                // ...&lt;br /&gt;
            },&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once handlers have been matched for a link, the actions will be fetched for all the matching handlers, in priorty order. The first valid action will be used to open the link.&lt;br /&gt;
&lt;br /&gt;
If your handler is matched with a link, but a condition assessed in the &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method means you want to revert to the next highest priorty handler, you can invalidate your action by settings its sites propety to an empty array.&lt;br /&gt;
======Complex example======&lt;br /&gt;
This will match all URLs containing &amp;lt;code&amp;gt;/mod/foo/&amp;lt;/code&amp;gt;, and force those with an id parameter that&#039;s not in the &amp;lt;code&amp;gt;supportedModFoos&amp;lt;/code&amp;gt; array to open in the user&#039;s browser, rather than the app.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const that = this;&lt;br /&gt;
const supportedModFoos = [...];&lt;br /&gt;
&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = new RegExp(&#039;\/mod\/foo\/&#039;);&lt;br /&gt;
        this.name = &#039;AddonModFooLinkHandler&#039;;&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {     &lt;br /&gt;
        const action = {&lt;br /&gt;
            action() {&lt;br /&gt;
                that.CoreUtilsProvider.openInBrowser(url);&lt;br /&gt;
            },&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (supportedModFoos.indexOf(parseInt(params.id)) !== -1) {&lt;br /&gt;
            action.sites = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return [action];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreContentLinksDelegate.registerHandler(new AddonModFooLinkHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Module prefetch handler=====&lt;br /&gt;
The &amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt; handler allows you to define a list of offline functions to prefetch a module. However, you might want to create your own prefetch handler to determine what needs to be downloaded. For example, you might need to chain WS calls (pass the result of a WS call to the next one), and this cannot be done using offline functions.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to create a prefetch handler using the initialisation JS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Create a class that extends from CoreCourseActivityPrefetchHandlerBase.&lt;br /&gt;
class AddonModCertificateModulePrefetchHandler extends CoreCourseActivityPrefetchHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.name = &#039;AddonModCertificateModulePrefetchHandler&#039;;&lt;br /&gt;
        this.modName = &#039;certificate&#039;;&lt;br /&gt;
&lt;br /&gt;
        // This must match the plugin identifier from db/mobile.php,&lt;br /&gt;
        // otherwise the download link in the context menu will not update correctly.&lt;br /&gt;
        this.component = &#039;mod_certificate&#039;;&lt;br /&gt;
        this.updatesNames = /^configuration$|^.*files$/;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Override the prefetch call.&lt;br /&gt;
    prefetch(module, courseId, single, dirPath) {&lt;br /&gt;
        return this.prefetchPackage(module, courseId, single, prefetchCertificate);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prefetchCertificate(module, courseId, single, siteId) {&lt;br /&gt;
    // Perform all the WS calls.&lt;br /&gt;
    // You can access most of the app providers using that.ClassName. E.g. that.CoreWSProvider.call().&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseModulePrefetchDelegate.registerHandler(new AddonModCertificateModulePrefetchHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
One relatively simple full example is where you have a function that needs to work offline, but it has an additional argument other than the standard ones. You can imagine for this an activity like the book module, where it has multiple pages for the same &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;. The app will not automatically work with this situation — it will call the offline function with the standard arguments only — so you won&#039;t be able to prefetch all the possible parameters.&lt;br /&gt;
&lt;br /&gt;
To deal with this, you need to implement a web service in your Moodle component that returns the list of possible extra arguments, and then you can call this web service and loop around doing the same thing the app does when it prefetches the offline functions. Here is an example from a third-party module (showing only the actual prefetch function, the rest of the code is as above) where there are multiple values of a custom &amp;lt;code&amp;gt;section&amp;lt;/code&amp;gt; parameter for the mobile function &amp;lt;code&amp;gt;mobile_document_view&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function prefetchOucontent(module, courseId, single, siteId) {&lt;br /&gt;
    var component = &#039;mod_oucontent&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Get the site, first.&lt;br /&gt;
    return that.CoreSitesProvider.getSite(siteId).then(function(site) {&lt;br /&gt;
        // Read the list of pages in this document using a web service.&lt;br /&gt;
        return site.read(&#039;mod_oucontent_get_page_list&#039;, {&#039;cmid&#039;: module.id}).then(function(response) {&lt;br /&gt;
            var promises = [];&lt;br /&gt;
&lt;br /&gt;
            // For each page, read and process the page - this is a copy of logic in the app at&lt;br /&gt;
            // siteplugins.ts (prefetchFunctions), but modified to add the custom argument.&lt;br /&gt;
            for(var i = 0; i &amp;lt; response.length; i++) {&lt;br /&gt;
                var args = {&lt;br /&gt;
                    courseid: courseId,&lt;br /&gt;
                    cmid: module.id,&lt;br /&gt;
                    userid: site.getUserId()&lt;br /&gt;
                };&lt;br /&gt;
                if (response[i] !== &#039;&#039;) {&lt;br /&gt;
                    args.section = response[i];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                promises.push(that.CoreSitePluginsProvider.getContent(&lt;br /&gt;
                        component, &#039;mobile_document_view&#039;, args).then(&lt;br /&gt;
                        function(result) {&lt;br /&gt;
                            var subPromises = [];&lt;br /&gt;
                            if (result.files &amp;amp;&amp;amp; result.files.length) {&lt;br /&gt;
                                subPromises.push(that.CoreFilepoolProvider.downloadOrPrefetchFiles(&lt;br /&gt;
                                        site.id, result.files, true, false, component, module.id));&lt;br /&gt;
                            }&lt;br /&gt;
                            return Promise.all(subPromises);&lt;br /&gt;
                        }));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(promises);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Single activity course format=====&lt;br /&gt;
In the following example, the value of &amp;lt;code&amp;gt;INIT_TEMPLATES[&#039;main&#039;]&amp;lt;/code&amp;gt; is:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;componentClass&amp;quot; [data]=&amp;quot;data&amp;quot;&amp;gt;&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This template is returned by the initialisation method. And this is the JavaScript code returned:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatComponent {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.data = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ngOnChanges(changes) {&lt;br /&gt;
        var self = this;&lt;br /&gt;
&lt;br /&gt;
        if (this.course &amp;amp;&amp;amp; this.sections &amp;amp;&amp;amp; this.sections.length) {&lt;br /&gt;
            var module = this.sections[0] &amp;amp;&amp;amp; this.sections[0].modules &amp;amp;&amp;amp; this.sections[0].modules[0];&lt;br /&gt;
            if (module &amp;amp;&amp;amp; !this.componentClass) {&lt;br /&gt;
                that.CoreCourseModuleDelegate.getMainComponent(that.Injector, this.course, module).then((component) =&amp;gt; {&lt;br /&gt;
                    self.componentClass = component || that.CoreCourseUnsupportedModuleComponent;&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.data.courseId = this.course.id;&lt;br /&gt;
            this.data.module = module;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    doRefresh(refresher, done) {&lt;br /&gt;
        return Promise.resolve(this.dynamicComponent.callComponentFunction(&amp;quot;doRefresh&amp;quot;, [refresher, done]));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatHandler {&lt;br /&gt;
    &lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.name = &#039;singleactivity&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    isEnabled() {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canViewAllSections() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseTitle(course, sections) {&lt;br /&gt;
        if (sections &amp;amp;&amp;amp; sections[0] &amp;amp;&amp;amp; sections[0].modules &amp;amp;&amp;amp; sections[0].modules[0]) {&lt;br /&gt;
            return sections[0].modules[0].name;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return course.fullname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displayEnableDownload() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displaySectionSelector() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseFormatComponent() {&lt;br /&gt;
        return that.CoreCompileProvider.instantiateDynamicComponent(that.INIT_TEMPLATES[&#039;main&#039;], AddonSingleActivityFormatComponent);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseFormatDelegate.registerHandler(new AddonSingleActivityFormatHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using the JavaScript API===&lt;br /&gt;
The JavaScript API is only supported by the delegates specified in the [[#Templates_downloaded_on_login_and_rendered_using_JS_data_2|Templates downloaded on login and rendered using JS data]] section. This API allows you to override any of the functions of the default handler.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; specified in a handler registered in the &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; will be called immediately after the initialisation method, and the JavaScript returned by this method will be run. If this JavaScript code returns an object with certain functions, these functions will override the ones in the default handler.&lt;br /&gt;
&lt;br /&gt;
For example, if the JavaScript returned by the method returns something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The the &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function of the default handler will be overridden by the returned &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
The default handler for &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; only has 2 functions: &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt;. In addition, the JavaScript code can return an extra function named &amp;lt;code&amp;gt;componentInit&amp;lt;/code&amp;gt; that will be executed when the component returned by &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; is initialised.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to support the text user profile field using this API:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
var result = {&lt;br /&gt;
    componentInit: function() {&lt;br /&gt;
        if (this.field &amp;amp;&amp;amp; this.edit &amp;amp;&amp;amp; this.form) {&lt;br /&gt;
            this.field.modelName = &#039;profile_field_&#039; + this.field.shortname;&lt;br /&gt;
&lt;br /&gt;
            if (this.field.param2) {&lt;br /&gt;
                this.field.maxlength = parseInt(this.field.param2, 10) || &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.field.inputType = that.CoreUtilsProvider.isTrueOrOne(this.field.param3) ? &#039;password&#039; : &#039;text&#039;;&lt;br /&gt;
&lt;br /&gt;
            var formData = {&lt;br /&gt;
                value: this.field.defaultdata,&lt;br /&gt;
                disabled: this.disabled,&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            this.form.addControl(this.field.modelName,&lt;br /&gt;
                that.FormBuilder.control(formData, this.field.required &amp;amp;&amp;amp; !this.field.locked ? that.Validators.required : null));&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        var name = &#039;profile_field_&#039; + field.shortname;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            type: &amp;quot;text&amp;quot;,&lt;br /&gt;
            name: name,&lt;br /&gt;
            value: that.CoreTextUtilsProvider.cleanTags(formValues[name]),&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Translate dynamic strings===&lt;br /&gt;
If you wish to have an element that displays a localised string based on value from your template you can doing something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_myactivity.&amp;lt;% status %&amp;gt;&#039; | translate }}&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This could save you from having to write something like when only one value should be displayed:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        &amp;lt;%#isedting%&amp;gt;{{ &#039;plugin.mod_myactivity.editing&#039; | translate }}&amp;lt;%/isediting%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isopen%&amp;gt;{{ &#039;plugin.mod_myactivity.open&#039; | translate }}&amp;lt;%/isopen%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isclosed%&amp;gt;{{ &#039;plugin.mod_myactivity.closed&#039; | translate }}&amp;lt;%/isclosed%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using strings with dates===&lt;br /&gt;
If you have a string that you wish to pass a formatted date, for example in the Moodle language file you have:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;strwithdate&#039;] = &#039;This string includes a date of {$a-&amp;gt;date} in the middle of it.&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can localise the string correctly in your template using something like the following:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{ &#039;plugin.mod_myactivity.strwithdate&#039; | translate: {$a: { date: &amp;lt;% timestamp %&amp;gt; * 1000 | coreFormatDate: &amp;quot;dffulldate&amp;quot; } } }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A Unix timestamp must be multiplied by 1000 as the Mobile App expects millisecond timestamps, whereas Unix timestamps are in seconds.&lt;br /&gt;
===Support push notification clicks===&lt;br /&gt;
If your plugin sends push notifications to the app, you might want to open a certain page in the app when the notification is clicked. There are several ways to achieve this.&lt;br /&gt;
&lt;br /&gt;
The easiest way is to include a &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; in your notification. When the notification is clicked, the app will try to open the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; will also be displayed in web. If you want to use a specific URL for the app, different than the one displayed in web, you can do so by returning a &amp;lt;code&amp;gt;customdata&amp;lt;/code&amp;gt; array that contains an &amp;lt;code&amp;gt;appurl&amp;lt;/code&amp;gt; property:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$notification-&amp;gt;customdata = [&lt;br /&gt;
    &#039;appurl&#039; =&amp;gt; $myurl-&amp;gt;out(),&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In both cases you will have to create a link handler to treat the URL. For more info on how to create the link handler, please see [[#Advanced_link_handler|how to create an advanced link handler]].&lt;br /&gt;
&lt;br /&gt;
If you want to do something that only happens when the notification is clicked, not when the link is clicked, you&#039;ll have to implement a push click handler yourself. The way to create it is similar to [[#Advanced_link_handler|creating an advanced link handler]], but you&#039;ll have to use &amp;lt;code&amp;gt;CorePushNotificationsDelegate&amp;lt;/code&amp;gt; and your handler will have to implement the properties and functions defined in the [https://github.com/moodlehq/moodleapp/blob/master/src/core/features/pushnotifications/services/push-delegate.ts#L27 CorePushNotificationsClickHandler] interface.&lt;br /&gt;
===Implement a module similar to mod_label===&lt;br /&gt;
In Moodle 3.8 or higher, if your plugin doesn&#039;t support &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt; and you don&#039;t specify a &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; then the module will only display the module description in the course page and it won&#039;t be clickable in the app, just like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt;. You can decide if you want the module icon to be displayed or not (if you don&#039;t want it to be displayed, then don&#039;t define it in &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
However, if your plugin needs to work in previous versions of Moodle or you want to display something different than the description then you need a different approach.&lt;br /&gt;
&lt;br /&gt;
If your plugin wants to render something in the course page instead of just the module name and description you should specify the &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. The template returned by this method will be rendered in the course page. Please notice the HTML returned should not contain directives or components, only plain HTML.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want your module to be clickable then you just need to remove &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. With these 2 changes you can have a module that behaves like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt; in the app.&lt;br /&gt;
===Use Ionic navigation lifecycle functions===&lt;br /&gt;
Ionic let pages define some functions that will be called when certain navigation lifecycle events happen. For more info about these functions, see [https://ionicframework.com/docs/api/router-outlet Ionic&#039;s documentation].&lt;br /&gt;
&lt;br /&gt;
You can define these functions in your plugin javascript:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.ionViewWillLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In addition to that, you can also implement &amp;lt;code&amp;gt;canLeave&amp;lt;/code&amp;gt; to use Angular route guards:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.canLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So for example you can make your plugin ask for confirmation if the user tries to leave the page when he has some unsaved data.&lt;br /&gt;
===Module plugins: dynamically determine if a feature is supported===&lt;br /&gt;
In Moodle you can specify if your plugin supports a certain feature, like &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. If your plugin will always support or not a certain feature, then you can use the &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; to specify it ([[#Options_only_for_CoreCourseModuleDelegate|see more documentation about this]]). But if you need to calculate it dynamically then you will have to create a function to calculate it. &lt;br /&gt;
&lt;br /&gt;
This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section above). The JavaScript returned by your initialisation method will need to define a function named &amp;lt;code&amp;gt;supportsFeature&amp;lt;/code&amp;gt; that will receive the name of the feature:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    supportsFeature: function(featureName) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;.&lt;br /&gt;
== Testing ==&lt;br /&gt;
You can also write automated tests for your plugin using Behat, you can read more about it on the [[Acceptance testing for the Moodle App]] page.&lt;br /&gt;
== Upgrading plugins from an older version ==&lt;br /&gt;
If you added mobile support to your plugin for the Ionic 3 version of the app (previous to the 3.9.5 release), you will probably need to make some changes to make it compatible with Ionic 5.&lt;br /&gt;
&lt;br /&gt;
Learn more at the [[Moodle App Plugins Upgrade Guide]].&lt;br /&gt;
==Troubleshooting==&lt;br /&gt;
=== Invalid response received ===&lt;br /&gt;
You might receive this error when using the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive or similar. By default, the app expects all Web Service calls to return an object, if your Web Service returns another type (string, boolean, etc.) then you need to specify it using the &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; attribute of the directive. For example, if your WS returns a boolean value, then you should specify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{typeExpected: &#039;boolean&#039;}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In a similar way, if your Web Service returns &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; you need to tell the app not to expect any result using &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{responseExpected: false}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS ===&lt;br /&gt;
Some directives allow you to specify a form id or name to send the data from the form to a certain WS. These directives look for HTML inputs to retrieve the data to send. However, &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; don&#039;t use HTML inputs, they simulate them, so the directive isn&#039;t going to find their data and so it won&#039;t be sent to the Web Service.&lt;br /&gt;
&lt;br /&gt;
There are 2 workarounds to fix this problem.&lt;br /&gt;
==== Sending the data manually ====&lt;br /&gt;
The first solution is to send the missing params manually using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; property. We will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input value in a variable, and this variable will be passed to the parameters. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a template like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group name=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, responses: responses}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Basically, you need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to the affected element (in this case, the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt;). You can put whatever name you want as the value, we used &amp;quot;responses&amp;quot;. With this, every time the user selects a radio button the value will be stored in a variable called &amp;quot;responses&amp;quot;. Then, in the button we are passing this variable to the parameters of the Web Service.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute has priority over &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;, so if you have an input with &amp;lt;code&amp;gt;name=&amp;quot;responses&amp;quot;&amp;lt;/code&amp;gt; it will override what you&#039;re manually passing to &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;.&lt;br /&gt;
==== Using a hidden input ====&lt;br /&gt;
Since the directive is looking for HTML inputs, you need to add one with the value to send to the server. You can use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to synchronise your radio/checkbox/select with the new hidden input. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a radio button like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot; [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;hidden&amp;quot; [ngModel]=&amp;quot;responses&amp;quot; name=&amp;quot;responses&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we&#039;re using a variable called &amp;quot;responses&amp;quot; to synchronise the data between the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt; and the hidden input. You can use whatever name you want.&lt;br /&gt;
=== I can&#039;t return an object or array in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; ===&lt;br /&gt;
If you try to return an object or an array in any field inside &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, the Web Service call will fail with the following error:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Scalar type expected, array or object received&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Each field in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; must be a string, number or boolean; it cannot be an object or array. To make it work, you need to encode your object or array into a JSON string:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($data)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app will automatically parse this JSON and convert it back into an array or object.&lt;br /&gt;
==Examples==&lt;br /&gt;
===Accepting dynamic names in a Web Service===&lt;br /&gt;
We want to display a form where the names of the fields are dynamic, like it happens in quiz. This data will be sent to a new Web Service that we have created.&lt;br /&gt;
&lt;br /&gt;
The first issue we find is that the Web Service needs to define the names of the parameters received, but in this case they&#039;re dynamic. The solution is to accept an array of objects with name and value. So in the &amp;lt;code&amp;gt;_parameters()&amp;lt;/code&amp;gt; function of our new Web Service, we will add this parameter:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;data&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
     new external_single_structure(&lt;br /&gt;
        [&lt;br /&gt;
            &#039;name&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data name&#039;),&lt;br /&gt;
            &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data value&#039;),&lt;br /&gt;
        ]&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;The data to be saved&#039;, VALUE_DEFAULT, []&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now we need to adapt our form to send the data as the Web Service requires it. In our template, we have a button with the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive that will send the form data to our Web Service. To make this work we will have to pass the parameters manually, without using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute, because we need to format the data before it is sent.&lt;br /&gt;
&lt;br /&gt;
Since we will send the parameters manually and we want it all to be sent in the same array, we will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input data into a variable that we&#039;ll call &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;, but you can use the name you want. This variable will be an object that will hold the input data with the format &amp;quot;name-&amp;gt;value&amp;quot;. For example, if I have an input with name &amp;quot;a1&amp;quot; and value &amp;quot;My answer&amp;quot;, the data object will be:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{a1: &#039;My answer&#039;}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So we need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to all the inputs whose values need to be sent to the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; WS param. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-input name=&amp;quot;&amp;lt;% name %&amp;gt;&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.data[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re using &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; to store the data. We do it like this because we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the form, setting the values the user has already stored. If you don&#039;t need to initialise the form, then you can use the &amp;lt;code&amp;gt;dataObject&amp;lt;/code&amp;gt; variable, an empty object that the mobile app creates for you:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[(ngModel)]=&amp;quot;dataObject[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app has a function that allows you to convert this data object into an array like the one the WS expects: &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt;. So in our button we&#039;ll use this function to format the data before it&#039;s sent:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_ws_name&amp;quot;&lt;br /&gt;
    [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, data: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.data, &#039;name&#039;, &#039;value&#039;)}&amp;quot;&lt;br /&gt;
    successMessage&lt;br /&gt;
    refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see in the example above, we&#039;re specifying that the keys of the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object need to be stored in a property called &amp;quot;name&amp;quot;, and the values need to be stored in a property called &amp;quot;value&amp;quot;. If your Web Service expects different names you need to change the parameters of the &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
If you open your plugin now in the app it will display an error in the JavaScript console. The reason is that the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; variable doesn&#039;t exist inside &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. As it is explained in previous sections, &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; holds the data that you return in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; for your method. We&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the values to be displayed in the form.&lt;br /&gt;
&lt;br /&gt;
If the user hasn&#039;t answered the form yet, we can initialise the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object as an empty object. Please remember that we cannot return arrays or objects in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, so we&#039;ll return a JSON string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; &#039;{}&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
With the code above, the form will always be empty when the user opens it. But now we want to check if the user has already answered the form and fill the form with the previous values. We will do it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$userdata = get_user_responses(); // It will held the data in a format name-&amp;gt;value. Example: [&#039;a1&#039; =&amp;gt; &#039;My value&#039;].&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($userdata)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now the user will be able to see previous values when the form is opened, and clicking the button will send the data to our Web Service in array format.&lt;br /&gt;
==Moodle plugins with mobile support==&lt;br /&gt;
* Group choice: [https://moodle.org/plugins/mod_choicegroup Moodle plugins directory entry] and [https://github.com/ndunand/moodle-mod_choicegroup code in github].&lt;br /&gt;
* Custom certificate: [https://moodle.org/plugins/mod_customcert Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_customcert code in github].&lt;br /&gt;
* Gapfill question type: [https://moodle.org/plugins/qtype_gapfill Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_gapfill in github].&lt;br /&gt;
* Wordselect question type: [https://moodle.org/plugins/qtype_wordselect Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_wordselect in github].&lt;br /&gt;
* RegExp question type: [https://moodle.org/plugins/qtype_regexp Moodle plugins directory entry] and [https://github.com/rezeau/moodle-qtype_regexp in github].&lt;br /&gt;
* Certificate: [https://moodle.org/plugins/mod_certificate Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_certificate in github].&lt;br /&gt;
* Attendance [https://moodle.org/plugins/mod_attendance Moodle plugins directory entry] and [https://github.com/danmarsden/moodle-mod_attendance in github].&lt;br /&gt;
* ForumNG (unfinished support) [https://moodle.org/plugins/mod_forumng Moodle plugins directory entry] and [https://github.com/moodleou/moodle-mod_forumng in github].&lt;br /&gt;
* News block [https://github.com/moodleou/moodle-block_news in github].&lt;br /&gt;
* H5P activity module [https://moodle.org/plugins/mod_hvp Moodle plugins directory entry] and [https://github.com/h5p/h5p-moodle-plugin in github].&lt;br /&gt;
See the complete list in [https://moodle.org/plugins/browse.php?list=award&amp;amp;id=6 the plugins database] (it may contain some outdated plugins).&lt;br /&gt;
=== Mobile app support award ===&lt;br /&gt;
If you want your plugin to be awarded in the plugins directory and marked as supporting the mobile app, please feel encouraged to contact us via email at [mailto:mobile@moodle.com mobile@moodle.com].&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to include a link to your plugin page and the location of its code repository.&lt;br /&gt;
&lt;br /&gt;
See [https://moodle.org/plugins/?q=award:mobile-app the list of awarded plugins] in the plugins directory.&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Moodle App Ionic 5]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62058</id>
		<title>Moodle App Plugins Development Guide</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62058"/>
		<updated>2022-04-25T08:20:45Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: /* Options only for CoreMainMenuDelegate */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle App (Ionic 5)}}&lt;br /&gt;
If you want to add mobile support to your Moodle plugin, you can achieve it by extending different areas of the app using &#039;&#039;just PHP server side code&#039;&#039; and providing templates written with [https://ionicframework.com/docs/components Ionic] and custom components.&lt;br /&gt;
&lt;br /&gt;
You will have to:&lt;br /&gt;
# Create a &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file in your plugin. In this file, you will be able to indicate which areas of the app you want to extend. For example, adding a new option in the main menu, implementing support for a new activity module, including a new option in the course menu, including a new option in the user profile, etc. All the areas supported are described further in this document.&lt;br /&gt;
# Create new functions in a reserved namespace that will return the content of the new options. The content should be returned rendered as html. This html should use Ionic components so that it looks native, but it can be generated using mustache templates.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let’s clarify some points:&lt;br /&gt;
* You don’t need to create new Web Service functions (although you will be able to use them for advanced features). You just need plain php functions that will be placed in a reserved namespace.&lt;br /&gt;
* Those functions will be exported via the Web Service function &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt;.&lt;br /&gt;
* As arguments of your functions you will always receive the &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, some relevant details of the app (like the app version or the current language in the app), and some specific data depending on the type of plugin (&amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
* The mobile app also implements a list of custom Ionic components and directives that provide dynamic behaviour; like indicating that you are linking a file that can be downloaded, allowing a transition to new pages into the app calling a specific function in the server, submitting form data to the server, etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Getting started==&lt;br /&gt;
If you only want to write a plugin, it is not necessary that you set up your environment to work with the Moodle App. In fact, you don&#039;t even need to compile it. You can just [[Using the Moodle App in a browser|use a Chromium-based browser]] to add mobile support to your plugins!&lt;br /&gt;
&lt;br /&gt;
You can use the app from one of the hosted versions on [https://master.apps.moodledemo.net master.apps.moodledemo.net] (the latest stable version) and [https://integration.apps.moodledemo.net integration.apps.moodledemo.net] (the latest development version). If you need any specific environment (hosted versions are deployed with a &#039;&#039;production&#039;&#039; environment), you can also use [[Moodle App Docker Images|Docker images]]. And if you need to test your plugin in a native device, you can always use [https://download.moodle.org/mobile Moodle HQ&#039;s application].&lt;br /&gt;
&lt;br /&gt;
This should suffice for developing plugins. However, if you are working on advanced functionality and you need to run the application from the source code, you can find more information in the [[Moodle App Development Guide]].&lt;br /&gt;
===Development workflow===&lt;br /&gt;
Before getting into the specifics of your plugin, we recommend that you start adding a simple &amp;quot;Hello World&amp;quot; button in the app to see that everything works properly.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s say your plugin is called &amp;lt;code&amp;gt;local_hello&amp;lt;/code&amp;gt;, you can start by adding the following files:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;local_hello&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [&lt;br /&gt;
            &#039;hello&#039; =&amp;gt; [&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreMainMenuDelegate&#039;,&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;view_hello&#039;,&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;title&#039; =&amp;gt; &#039;hello&#039;,&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; &#039;earth&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [&lt;br /&gt;
            [&#039;hello&#039;, &#039;local_hello&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace local_hello\output;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    public static function view_hello() {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; &#039;&amp;lt;h1 class=&amp;quot;text-center&amp;quot;&amp;gt;{{ &amp;quot;plugin.local_hello.hello&amp;quot; | translate }}&amp;lt;/h1&amp;gt;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;lang/en/local_hello.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;hello&#039;] = &#039;Hello World&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once you&#039;ve done that, try logging into your site in the app and you should see a new button in the main menu or more menu (depending on the device) saying &amp;quot;Hello World&amp;quot;. If you press this button, you should see a page saying &amp;quot;Hello World!&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Congratualtions, you have written your first Moodle plugin with moodle support!&lt;br /&gt;
&lt;br /&gt;
You can read the rest of this page to learn more about mobile plugins and start working on your plugin. Here&#039;s some things to keep in mind:&lt;br /&gt;
* If you change the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file, you will have to refresh the browser. And remember to [https://developer.chrome.com/docs/devtools/network/reference/#disable-cache disable the network cache].&lt;br /&gt;
* If you change an existing template or function, you won’t have to refresh the browser. In most cases, doing a PTR (Pull To Refresh) in the page that displays the template will suffice.&lt;br /&gt;
* If any of these doesn&#039;t show your changes, you may need to [https://docs.moodle.org/311/en/Developer_tools#Purge_all_caches purge all caches] to avoid problems with the auto-loading cache.&lt;br /&gt;
* Ultimately, if that doesn&#039;t work either, you may have to log out from the site and log in again. If any changes affect plugin installation, you may also need to increase the version in your plugin&#039;s &amp;lt;code&amp;gt;version.php&amp;lt;/code&amp;gt; file and upgrade it in the site.&lt;br /&gt;
==Types of plugins==&lt;br /&gt;
There are 3 types of plugins:&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
[[File:Templates_downloaded_when_requested.png|thumb]]&lt;br /&gt;
With this type of plugin, the template of your plugin will be generated and downloaded when the user opens the plugin in the app. This means that your function will receive some context parameters. For example, if you&#039;re developing a course module plugin you will receive the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; (course module ID). You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
[[File:Templates_downloaded_on_login.png|thumb]]&lt;br /&gt;
With this type of plugin, the template for your plugin will be downloaded when the user logs in into the app and will be stored in the device. This means that your function will not receive any context parameters, and you need to return a generic template that will be built with JS data like the ones in the Moodle App. When the user opens a page that includes your plugin, your template will receive the required JS data and your template will be rendered. You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
You can always implement the whole plugin yourself using JavaScript instead of using our API. In fact, this is required if you want to implement some features like capturing links in the Mobile app. You can see the list of delegates that only support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
==Step by step example==&lt;br /&gt;
In this example, we are going to update an existing plugin, the [https://github.com/mdjnelson/moodle-mod_certificate Certificate activity module], that previously used a [[Moodle Mobile 2 (Ionic 1) Remote add-ons|Remote add-on]] (a legacy approach to implement mobile plugins).&lt;br /&gt;
&lt;br /&gt;
This is a simple activity module that displays the certificate issued for the current user along with the list of the dates of previously issued certificates. It also stores in the course log that the user viewed a certificate. This module also works offline: when the user downloads the course or activity, the data is pre-fetched and can be viewed offline.&lt;br /&gt;
===Step 1. Update the &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file===&lt;br /&gt;
In this case, we are updating an existing file. For new plugins, you should create this new file.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;mod_certificate&#039; =&amp;gt; [ // Plugin identifier&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;coursecertificate&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/mod/certificate/pix/icon.gif&#039;,&lt;br /&gt;
                    &#039;class&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
       &lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseModuleDelegate&#039;, // Delegate (where to display the link to the plugin)&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;mobile_course_view&#039;, // Main function in \mod_certificate\output\mobile&lt;br /&gt;
                &#039;offlinefunctions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;mobile_course_view&#039; =&amp;gt; [],&lt;br /&gt;
                    &#039;mobile_issues_view&#039; =&amp;gt; [],&lt;br /&gt;
                ], // Function that needs to be downloaded for offline.&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [ // Language strings that are used in all the handlers.&lt;br /&gt;
            [&#039;pluginname&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;summaryofattempts&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;getcertificate&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;requiredtimenotmet&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;viewcertificateviews&#039;, &#039;certificate&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Plugin identifier:&lt;br /&gt;
: A unique name for the plugin, it can be anything (there’s no need to match the module name).&lt;br /&gt;
&lt;br /&gt;
;Handlers (Different places where the plugin will display content):&lt;br /&gt;
: A plugin can be displayed in different views in the app. Each view should have a unique name inside the plugin scope (alphanumeric).&lt;br /&gt;
&lt;br /&gt;
; Display data:&lt;br /&gt;
: This is only needed for certain types of plugins. Also, depending on the type of delegate it may require additional (or less fields). In this case, we are indicating the module icon.&lt;br /&gt;
&lt;br /&gt;
; Delegate&lt;br /&gt;
: Where to display the link to the plugin, see the [[#Delegates|Delegates]] section for all the possible options.&lt;br /&gt;
&lt;br /&gt;
; Method:&lt;br /&gt;
: This is the method in the Moodle &amp;lt;code&amp;gt;\{component-name}\output\mobile&amp;lt;/code&amp;gt; class to be executed the first time the user clicks in the new option displayed in the app.&lt;br /&gt;
&lt;br /&gt;
; Offlinefunctions&lt;br /&gt;
: This is the list of functions that need to be called and stored when the user downloads a course for offline usage. Please note that you can add functions here that are not even listed in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. &lt;br /&gt;
: In our example, downloading for offline access will mean that we&#039;ll execute the functions for getting the certificate and issued certificates passing as parameters the current &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; (and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; when we are using the mod or course delegate). If we have the result of those functions stored in the app, we&#039;ll be able to display the certificate information even if the user is offline.&lt;br /&gt;
: Offline functions will be mostly used to display information for final users, any further interaction with the view won’t be supported offline (for example, trying to send information when the user is offline).&lt;br /&gt;
: You can indicate here other Web Services functions, indicating the parameters that they might need from a defined subset (currently &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Prefetching the module will also download all the files returned by the methods in these offline functions (in the &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; array).&lt;br /&gt;
: Note that if your functions use additional custom parameters (for example, if you implement multiple pages within a module&#039;s view function by using a &amp;lt;code&amp;gt;page&amp;lt;/code&amp;gt; parameter in addition to the usual &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;) then the app will not know which additional parameters to supply. In this case, do not list the function in &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt;; instead, you will need to manually implement a [[#Module_prefetch_handler|module prefetch handler]].&lt;br /&gt;
&lt;br /&gt;
;Lang:&lt;br /&gt;
: The language pack string ids used in the plugin by all the handlers. Normally these will be strings from your own plugin, however, you can list any strings you need here, like &amp;lt;code&amp;gt;[&#039;cancel&#039;, &#039;moodle&#039;]&amp;lt;/code&amp;gt;. If you do this, be warned that in the app you will then need to refer to that string as &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.myplugin.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (not &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.moodle.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Please only include the strings you actually need. The Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform, and this will then be cached, so listing too many strings is very wasteful.&lt;br /&gt;
There are additional attributes supported by the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; list, you can find about them in the [[#Mobile.php_supported_options|Mobile.php supported options]] section.&lt;br /&gt;
===Step 2. Creating the main function===&lt;br /&gt;
The main function displays the current issued certificate (or several warnings if it’s not possible to issue a certificate). It also displays a link to view the dates of previously issued certificates.&lt;br /&gt;
&lt;br /&gt;
All the functions must be created in the plugin or subsystem &amp;lt;code&amp;gt;classes/output&amp;lt;/code&amp;gt; directory, the name of the class must be &amp;lt;code&amp;gt;mobile&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
For this example, the namespace name will be &amp;lt;code&amp;gt;mod_certificate\output&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace mod_certificate\output;&lt;br /&gt;
&lt;br /&gt;
use context_module;&lt;br /&gt;
use mod_certificate_external;&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Returns the certificate course view for the mobile app.&lt;br /&gt;
     *&lt;br /&gt;
     * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
     *&lt;br /&gt;
     * @return array       HTML, JS and other data.&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
        $args = (object) $args;&lt;br /&gt;
        $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
        // Capabilities check.&lt;br /&gt;
        require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
        $context = \context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
        require_capability(&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
        if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
            require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
        }&lt;br /&gt;
        $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
        // Get certificates from external (taking care of exceptions).&lt;br /&gt;
        try {&lt;br /&gt;
            $issued = \mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
            $certificates = \mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
            $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $issues = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Set timemodified for each certificate.&lt;br /&gt;
        foreach ($issues as $issue) {&lt;br /&gt;
            if (empty($issue-&amp;gt;timemodified)) {&lt;br /&gt;
                $issue-&amp;gt;timemodified = $issue-&amp;gt;timecreated;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $showget = true;&lt;br /&gt;
        if ($certificate-&amp;gt;requiredtime &amp;amp;&amp;amp; !has_capability(&#039;mod/certificate:manage&#039;, $context)) {&lt;br /&gt;
            if (certificate_get_course_time($certificate-&amp;gt;course) &amp;lt; ($certificate-&amp;gt;requiredtime * 60)) {&lt;br /&gt;
                $showget = false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $certificate-&amp;gt;name = format_string($certificate-&amp;gt;name);&lt;br /&gt;
        [$certificate-&amp;gt;intro, $certificate-&amp;gt;introformat] =&lt;br /&gt;
                external_format_text($certificate-&amp;gt;intro, $certificate-&amp;gt;introformat, $context-&amp;gt;id, &#039;mod_certificate&#039;, &#039;intro&#039;);&lt;br /&gt;
        $data = [&lt;br /&gt;
            &#039;certificate&#039; =&amp;gt; $certificate,&lt;br /&gt;
            &#039;showget&#039; =&amp;gt; $showget &amp;amp;&amp;amp; count($issues) &amp;gt; 0,&lt;br /&gt;
            &#039;issues&#039; =&amp;gt; $issues,&lt;br /&gt;
            &#039;issue&#039; =&amp;gt; $issues[0],&lt;br /&gt;
            &#039;numissues&#039; =&amp;gt; count($issues),&lt;br /&gt;
            &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
            &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;files&#039; =&amp;gt; $issues,&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Function declaration: &lt;br /&gt;
: The function name is the same as the one used in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file (&amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; field). There is only one argument, &amp;lt;code&amp;gt;$args&amp;lt;/code&amp;gt;, which is an array containing all the information sent by the mobile app (the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversionname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversioncode&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;applang&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appcustomurlscheme&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
&lt;br /&gt;
; Function implementation:&lt;br /&gt;
: In the first part of the function, we check permissions and capabilities (like a &amp;lt;code&amp;gt;view.php&amp;lt;/code&amp;gt; script would do normally). Then we retrieve the certificate information that’s necessary to display the template.&lt;br /&gt;
&lt;br /&gt;
; Function return:&lt;br /&gt;
* &amp;lt;code&amp;gt;templates&amp;lt;/code&amp;gt; — The rendered template (notice that we could return more than one template, but we usually would only need one). By default the app will always render the first template received, the rest of the templates can be used if the plugin defines some JavaScript code.&lt;br /&gt;
* &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; — Empty, because we don’t need any in this case.&lt;br /&gt;
* &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; — Empty as well, because we don’t need any additional data to be used by directives or components in the template. This field will be published as an object supporting 2-way data-binding in the template.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; — A list of files that the app should be able to download (for offline usage mostly).&lt;br /&gt;
===Step 3. Creating the template for the main function===&lt;br /&gt;
This is the most important part of your plugin because it contains the code that will be rendered on the mobile app.&lt;br /&gt;
&lt;br /&gt;
In this template we’ll be using Ionic, together with directives and components specific to the Moodle App.&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;ion-&amp;lt;/code&amp;gt; are ionic components. Most of the time, the component name is self-explanatory but you may refer to a detailed guide here: https://ionicframework.com/docs/components/&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;core-&amp;lt;/code&amp;gt; are custom components of the Moodle App.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_page.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;core-course-module-description description=&amp;quot;&amp;lt;% certificate.intro %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-course-module-description&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;ion-list-header&amp;gt;&lt;br /&gt;
            &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.summaryofattempts&#039; | translate }}&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-list-header&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-new-content title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; &lt;br /&gt;
                        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
                        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
                    {{ &#039;plugin.mod_certificate.viewcertificateviews&#039; | translate: {$a: &amp;lt;% numissues %&amp;gt;} }}&lt;br /&gt;
                &amp;lt;/ion-button&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&lt;br /&gt;
                        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
                        [files]=&amp;quot;[{&lt;br /&gt;
                            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
                            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
                            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
                        }]&amp;quot;&amp;gt;&lt;br /&gt;
                 &lt;br /&gt;
                        &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot; slot=&amp;quot;start&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                            {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
                    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%^showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;p&amp;gt;{{ &#039;plugin.mod_certificate.requiredtimenotmet&#039; | translate }}&amp;lt;/p&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;!-- Call log WS when the template is loaded. --&amp;gt;&lt;br /&gt;
        &amp;lt;span core-site-plugins-call-ws-on-load name=&amp;quot;mod_certificate_view_certificate&amp;quot;&lt;br /&gt;
                [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/span&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache).&lt;br /&gt;
&lt;br /&gt;
Then we display the module description using &amp;lt;code&amp;gt;core-course-module-description&amp;lt;/code&amp;gt;, which is a component used to include the course module description.&lt;br /&gt;
&lt;br /&gt;
For displaying the certificate information we create a list of elements, adding a header on top.&lt;br /&gt;
&lt;br /&gt;
The following line using the &amp;lt;code&amp;gt;translate&amp;lt;/code&amp;gt; filter indicates that the app will translate the &amp;lt;code&amp;gt;summaryofattempts&amp;lt;/code&amp;gt; string id (here we could’ve used mustache translation but it is usually better to delegate the strings translations to the app). The string id has the following format: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
plugin.{plugin-identifier}.{string-id}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Where &amp;lt;code&amp;gt;{plugin-identifier}&amp;lt;/code&amp;gt; is taken from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;{string-id}&amp;lt;/code&amp;gt; must be indicated in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; field in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Then, we display a button to transition to another page if there are certificates issued. The attribute (directive) &amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt; indicates that if the user clicks the button, we need to call the &amp;lt;code&amp;gt;mobile_issues_view&amp;lt;/code&amp;gt; function in the &amp;lt;code&amp;gt;mod_certificate&amp;lt;/code&amp;gt; component; passing as arguments the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;. The content returned by this function will be displayed in a new page (read the following section to see the code of this new page).&lt;br /&gt;
&lt;br /&gt;
Just after this button, we display another one but this time for downloading an issued certificate. The &amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt; directive indicates that clicking this button is for downloading the whole activity and opening the main file. This means that, when the user clicks this button, the whole certificate activity will be available offline.&lt;br /&gt;
&lt;br /&gt;
Finally, just before the &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt; is closed, we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt; directive to indicate that once the page is loaded, we need to call a Web Service function in the server, in this case we are calling the &amp;lt;code&amp;gt;mod_certificate_view_certificate&amp;lt;/code&amp;gt; that will log that the user viewed this page.&lt;br /&gt;
&lt;br /&gt;
As you can see, no JavaScript was necessary at all. We used plain HTML elements and attributes that did all the complex dynamic logic (like calling a Web Service) behind the scenes.&lt;br /&gt;
&lt;br /&gt;
===Step 4. Adding an additional page===&lt;br /&gt;
Add the following method to &amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Returns the certificate issues view for the mobile app.&lt;br /&gt;
 * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
 *&lt;br /&gt;
 * @return array       HTML, JS and other data.&lt;br /&gt;
 */&lt;br /&gt;
public static function mobile_issues_view($args) {&lt;br /&gt;
    global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
    $args = (object) $args;&lt;br /&gt;
    $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
    // Capabilities check.&lt;br /&gt;
    require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
    $context = context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
    require_capability (&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
    if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
        require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
    }&lt;br /&gt;
    $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
    // Get certificates from external (taking care of exceptions).&lt;br /&gt;
    try {&lt;br /&gt;
        $issued = mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
        $certificates = mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
        $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
    } catch (Exception $e) {&lt;br /&gt;
        $issues = [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $data = [&#039;issues&#039; =&amp;gt; $issues];&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
            [&lt;br /&gt;
                &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_issues&#039;, $data),&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
        &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This method for the new page was added just after &amp;lt;code&amp;gt;mobile_course_view&amp;lt;/code&amp;gt;, the code is quite similar: checks the capabilities, retrieves the information required for the template, and returns the template rendered.&lt;br /&gt;
&lt;br /&gt;
The code of the mustache template is also very simple.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_issues.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &amp;lt;%timecreated%&amp;gt; | coreToLocaleString }}&amp;lt;/p&amp;gt;&lt;br /&gt;
                    &amp;lt;p&amp;gt;&amp;lt;%grade%&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As we did in the previous template, in the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache). &lt;br /&gt;
&lt;br /&gt;
Here we are creating an Ionic list that will display a new item in the list per each issued certificated.&lt;br /&gt;
&lt;br /&gt;
For the issued certificated we’ll display the time when it was created (using the app filter &amp;lt;code&amp;gt;coreToLocaleString&amp;lt;/code&amp;gt;). We are also displaying the grade displayed in the certificate (if any).&lt;br /&gt;
&lt;br /&gt;
===Step 5. Plugin webservices, if included===&lt;br /&gt;
If your plugin uses its own web services, they will also need to be enabled for mobile access in your &amp;lt;code&amp;gt;db/services.php&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
The following line should be included in each webservice definition:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;services&#039; =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/db/services.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$functions = [&lt;br /&gt;
    &#039;mod_certificate_get_certificates_by_courses&#039; =&amp;gt; [&lt;br /&gt;
        &#039;classname&#039;     =&amp;gt; &#039;mod_certificate_external&#039;,&lt;br /&gt;
        &#039;methodname&#039;    =&amp;gt; &#039;get_certificates_by_courses&#039;,&lt;br /&gt;
        &#039;description&#039;   =&amp;gt; &#039;Returns a list of certificate instances...&#039;,&lt;br /&gt;
        &#039;type&#039;          =&amp;gt; &#039;read&#039;,&lt;br /&gt;
        &#039;capabilities&#039;  =&amp;gt; &#039;mod/certificate:view&#039;,&lt;br /&gt;
        &#039;services&#039;      =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Mobile.php supported options==&lt;br /&gt;
In the previous section, we learned about some of the existing options for handlers configuration. This is the full list of supported options.&lt;br /&gt;
===Common options===&lt;br /&gt;
* &amp;lt;code&amp;gt;delegate&amp;lt;/code&amp;gt; (mandatory) — Name of the delegate to register the handler in.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (mandatory) — The method to call to retrieve the main page content.&lt;br /&gt;
* &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; (optional) — A method to call to retrieve the initialisation JS and the restrictions to apply to the whole handler. It can also return templates that can be used from JavaScript. You can learn more about this in the [[#Initialisation|Initialisation]] section.&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttocurrentuser&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForUser&amp;lt;/code&amp;gt; function. If true, the handler will only be shown for the current user. For more info about displaying the plugin only for certain users, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttoenrolledcourses&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForCourse&amp;lt;/code&amp;gt; function. If true or not defined, the handler will only be shown for courses the user is enrolled in. For more info about displaying the plugin only for certain courses, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;styles&amp;lt;/code&amp;gt; (optional) — An array with two properties: &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt;. The URL should point to a CSS file, either using an absolute URL or a relative URL. This file will be downloaded and applied by the app. It&#039;s recommended to include styles that will only affect your plugin templates. The version number is used to determine if the file needs to be downloaded again, you should change the version number everytime you change the CSS file.&lt;br /&gt;
* &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; (optional) — If your plugin supports a component in the app different than the one defined by your plugin, you can use this property to specify it. For example, you can create a local plugin to support a certain course format, activity, etc. The component of your plugin in Moodle would be &amp;lt;code&amp;gt;local_whatever&amp;lt;/code&amp;gt;, but in &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; you can specify that this handler will implement &amp;lt;code&amp;gt;format_whatever&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;mod_whatever&amp;lt;/code&amp;gt;. This property was introduced in the version 3.6.1 of the app.&lt;br /&gt;
===Options only for CoreMainMenuDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. [[Moodle_App_Plugins_Development_Guide#Using_.27font.27_icons_with_ion-icon|See icons section]].&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. Main Menu plugins are always displayed in the More tab, they cannot be displayed as tabs in the bottom bar.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreMainMenuHomeDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseOptionsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ismenuhandler&amp;lt;/code&amp;gt; (optional) — Supported from the 3.7.1 version of the app. Set it to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if you want your plugin to be displayed in the contextual menu of the course instead of in the top tabs. The contextual menu is displayed when you click in the 3-dots button at the top right of the course.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseModuleDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — Path to the module icon. After Moodle app 4.0, this icon is only used as a fallback, the app will always try to use the theme icon so themes can override icons in the app.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (optional) — The function to call to retrieve the main page content. In this delegate the method is optional. If the method is not set, the module won&#039;t be clickable.&lt;br /&gt;
* &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt; (optional) — List of functions to call when prefetching the module. It can be a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; method or a WS. You can filter the params received by the WS. By default, WS will receive these params: &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;. Other valid values that will be added if they are present in the list of params: &amp;lt;code&amp;gt;courseids&amp;lt;/code&amp;gt; (it will receive a list with the courses the user is enrolled in), &amp;lt;code&amp;gt;{component}id&amp;lt;/code&amp;gt; (For example, &amp;lt;code&amp;gt;certificateid&amp;lt;/code&amp;gt;).&lt;br /&gt;
* &amp;lt;code&amp;gt;downloadbutton&amp;lt;/code&amp;gt; (optional) — Whether to display download button in the module. If not defined, the button will be shown if there is any offlinefunction.&lt;br /&gt;
* &amp;lt;code&amp;gt;isresource&amp;lt;/code&amp;gt; (optional) — Whether the module is a resource or an activity. Only used if there is any offline function. If your module relies on the &amp;lt;code&amp;gt;contents&amp;lt;/code&amp;gt; field, then it should be &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;updatesnames&amp;lt;/code&amp;gt; (optional) — Only used if there is any offline function. A regular expression to check if there&#039;s any update in the module. It will be compared to the result of &amp;lt;code&amp;gt;core_course_check_updates&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayopeninbrowser&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Open in browser&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayOpenInBrowser = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydescription&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Description&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayDescription = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayrefresh&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Refresh&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayRefresh = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayprefetch&amp;lt;/code&amp;gt; (optional) — Whether the module should display the download option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayPrefetch = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysize&amp;lt;/code&amp;gt; (optional) — Whether the module should display the downloaded size in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displaySize = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; (optional) — It can be used to specify the supported features of the plugin. Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. It should be an array with features as keys (For example, &amp;lt;code&amp;gt;[FEATURE_NO_VIEW_LINK =&amp;gt; true&amp;lt;/code&amp;gt;). If you need to calculate this dynamically please see [[#Module_plugins:_dynamically_determine_if_a_feature_is_supported|Module plugins: dynamically determine if a feature is supported]]. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; (optional) — If set, this method will be called when the course is rendered and the HTML returned will be displayed in the course page for the module. Please notice the HTML returned should not contain directives or components, only default HTML. Supported from the 3.8 version of the app.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreCourseFormatDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;canviewallsections&amp;lt;/code&amp;gt; (optional) — Whether the course format allows seeing all sections in a single page. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayenabledownload&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, it&#039;s no longer used.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysectionselector&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, use &#039;&#039;displaycourseindex&#039;&#039; instead.&lt;br /&gt;
*&amp;lt;code&amp;gt;displaycourseindex&amp;lt;/code&amp;gt; (optional) — Whether the default course index should be displayed. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
===Options only for CoreUserDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — The type of the addon. The values accepted are &amp;lt;code&amp;gt;&#039;newpage&#039;&amp;lt;/code&amp;gt; (default) and &amp;lt;code&amp;gt;&#039;communication&#039;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreSettingsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for AddonMessageOutputDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreBlockDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (optional):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section. If this is not supplied, it will default to &amp;lt;code&amp;gt;&#039;plugins.block_{block-name}.pluginname&#039;&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class. If this is not supplied, it will default to &amp;lt;code&amp;gt;block_{block-name}&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — Possible values are:&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;title&amp;quot;&amp;lt;/code&amp;gt; — Your block will only display the block title, and when it&#039;s clicked it will open a new page to display the block contents (the template returned by the block&#039;s method).&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;prerendered&amp;quot;&amp;lt;/code&amp;gt; — Your block will display the content and footer returned by the WebService to get the blocks (for example, &amp;lt;code&amp;gt;core_block_get_course_blocks&amp;lt;/code&amp;gt;), so your block&#039;s method will never be called.&lt;br /&gt;
*** Any other value — Your block will immediately call the method specified in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and it will use the template to render the block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fallback&amp;lt;/code&amp;gt; (optional) — This option allows you to specify a block to use in the app instead of your block. For example, you can make the app display the &amp;quot;My overview&amp;quot; block instead of your block in the app by setting &amp;lt;code&amp;gt;&#039;fallback&#039; =&amp;gt; &#039;myoverview&#039;&amp;lt;/code&amp;gt;. The fallback will only be used if you don&#039;t specify a &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; is different to &amp;lt;code&amp;gt;&#039;title&#039;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&#039;prerendered&#039;&amp;lt;/code&amp;gt;. Supported from the 3.9.0 version of the app.&lt;br /&gt;
==Delegates==&lt;br /&gt;
Delegates can be classified by type of plugin. For more info about type of plugins, please see the [[#Types_of_plugins|Types of plugins]] section.&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new items to the main menu (currently displayed at the bottom of the app). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuHomeDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new tabs in the home page (by default the app is displaying the &amp;quot;Dashboard&amp;quot; and &amp;quot;Site home&amp;quot; tabs). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new options in a course (Participants or Grades are examples of this type of delegate).&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting activity modules or resources.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add additional options in the user profile page in the app.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting course formats. When you open a course from the course list in the mobile app, it will check if there is a &amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt; handler for the format that site uses. If so, it will display the course using that handler. Otherwise, it will use the default app course format.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile course formats]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreSettingsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to add a new option in the settings page.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonMessageOutputDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a message output plugin.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreBlockDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a block. For example, blocks can be displayed in Site Home, Dashboard and the Course page.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question types.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile question types]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionBehaviourDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question behaviours.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting user profile fields.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModQuizAccessRuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a quiz access rule.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModAssignSubmissionDelegate&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;AddonModAssignFeedbackDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use these delegates to support assign submission or feedback plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonWorkshopAssessmentStrategyDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a workshop assessment strategy plugin.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
These delegates require JavaScript to be supported. See [[#Initialisation|Initialisation]] for more information.&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreContentLinksDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreCourseModulePrefetchDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFileUploaderDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CorePluginFileDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFilterDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
==Available components and directives==&lt;br /&gt;
===Difference between components and directives===&lt;br /&gt;
A directive is usually represented as an HTML attribute, allows you to extend a piece of HTML with additional information or functionality. Example of directives are: &amp;lt;code&amp;gt;core-auto-focus&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*ngIf&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ng-repeat&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components are also directives, but they are usually represented as an HTML tag and they are used to add custom elements to the app. Example of components are &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-item&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;core-search-box&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components and directives are Angular concepts; you can learn more about them and the components come out of the box with Ionic in the following links:&lt;br /&gt;
* [https://angular.io/guide/built-in-directives Angular directives documentation]&lt;br /&gt;
* [https://ionicframework.com/docs/components Ionic components]&lt;br /&gt;
===Custom core components and directives===&lt;br /&gt;
These are some useful custom components and directives that are only available in the Moodle App. Please note that this isn’t the full list of custom components and directives, it’s just an extract of the most common ones.&lt;br /&gt;
&lt;br /&gt;
You can find a full list of components and directives in the source code of the app, within [https://github.com/moodlehq/moodleapp/tree/master/src/core/components &amp;lt;code&amp;gt;src/core/components&amp;lt;/code&amp;gt;] and [https://github.com/moodlehq/moodleapp/tree/master/src/core/directives &amp;lt;code&amp;gt;src/core/directives&amp;lt;/code&amp;gt;].&lt;br /&gt;
====&amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;====&lt;br /&gt;
This directive formats the text and adds some directives needed for the app to work as it should. For example, it treats all links and all the embedded media so they work fine in the app. If some content in your template includes links or embedded media, please use this directive.&lt;br /&gt;
&lt;br /&gt;
This directive automatically applies &amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt; to all the links and embedded media.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;text&amp;lt;/code&amp;gt; (string) — The text to format.&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;adaptImg&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to adapt images to screen width. &lt;br /&gt;
* &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether all HTML tags should be removed.&lt;br /&gt;
* &amp;lt;code&amp;gt;singleLine&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether new lines should be removed to display all the text in single line. Only if &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; (number) — Optional. Max height in pixels to render the content box. The minimum accepted value is 50. Using this parameter will force &amp;lt;code&amp;gt;display: block&amp;lt;/code&amp;gt; to calculate the height better. If you want to avoid this, use &amp;lt;code&amp;gt;class=&amp;quot;inline&amp;quot;&amp;lt;/code&amp;gt; at the same time to use &amp;lt;code&amp;gt;display: inline-block&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;fullOnClick&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether it should open a new page with the full contents on click. Only if &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; is set and the content has been collapsed. &lt;br /&gt;
* &amp;lt;code&amp;gt;fullTitle&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;Description&amp;quot;&amp;lt;/code&amp;gt;. Title to use in full view.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-format-text text=&amp;quot;&amp;lt;% cm.description %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle a link. It performs several checks, like checking if the link needs to be opened in the app, and opens the link as it should (without overriding the app).&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;capture&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether the link needs to be captured by the app (check if the link can be handled by the app instead of opening it in a browser).&lt;br /&gt;
* &amp;lt;code&amp;gt;inApp&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to open in an embedded browser within the app or in the system browser.&lt;br /&gt;
* &amp;lt;code&amp;gt;autoLogin&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt;. If the link should be open with auto-login. Accepts the following values:&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;yes&amp;quot;&amp;lt;/code&amp;gt; — Always auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;no&amp;quot;&amp;lt;/code&amp;gt; — Never auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt; — Auto-login only if it points to the current site.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a href=&amp;quot;&amp;lt;% cm.url %&amp;gt;&amp;quot; core-link&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle links to files and embedded files. This directive should be used in any link to a file or any embedded file that you want to have available when the app is offline. &lt;br /&gt;
&lt;br /&gt;
If a file is downloaded, its URL will be replaced by the local file URL.&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;&amp;lt;% event.iconurl %&amp;gt;&amp;quot; core-external-content component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% event.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-user-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to go to user profile on click. When the user clicks the element where this directive is attached, the right user profile will be opened.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;userId&amp;lt;/code&amp;gt; (number) — User id to open the profile.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — Optional. Course id to show the user info related to that course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a ion-item core-user-link userId=&amp;quot;&amp;lt;% userid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to handle a remote file. It shows the file name, icon (depending on mime type) and a button to download or refresh it. The user can identify if the file is downloaded or not based on the button.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;file&amp;lt;/code&amp;gt; (object) — The file. Must have a &amp;lt;code&amp;gt;filename&amp;lt;/code&amp;gt; property and either &amp;lt;code&amp;gt;fileurl&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component the file belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDelete&amp;lt;/code&amp;gt; (boolean) — Optional. Whether the file can be deleted.&lt;br /&gt;
* &amp;lt;code&amp;gt;alwaysDownload&amp;lt;/code&amp;gt; (boolean) — Optional. Whether it should always display the refresh button when the file is downloaded. Use it for files that you cannot determine if they&#039;re outdated or not.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDownload&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether file can be downloaded. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-file&lt;br /&gt;
        [file]=&amp;quot;{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.url %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.name %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            filesize: &#039;&amp;lt;% issue.size %&amp;gt;&#039;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/core-file&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening a file. When the item with this directive is clicked, the file will be downloaded (if needed) and opened.&lt;br /&gt;
&lt;br /&gt;
It is usually recommended to use the &amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt; component since it also displays the state of the file.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt; (object) — The file to download.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. Component ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage (a button to download a file):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button&lt;br /&gt;
        [core-download-file]=&amp;quot;{&lt;br /&gt;
            fileurl: &amp;lt;% issue.url %&amp;gt;,&lt;br /&gt;
            timemodified: &amp;lt;% issue.timemodified %&amp;gt;,&lt;br /&gt;
            filesize: &amp;lt;% issue.size %&amp;gt;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.download | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening the main file of a module.&lt;br /&gt;
&lt;br /&gt;
When the item with this directive is clicked, the whole module will be downloaded (if needed) and its main file opened. This is meant for modules like &amp;lt;code&amp;gt;mod_resource&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This directive must receive either a &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; or a &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. If no files are provided, it will use &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; (object) — Optional, required if module is not supplied. The module object.&lt;br /&gt;
* &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt; (number) — Optional, required if module is not supplied. The module ID.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — The course ID the module belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional, defaults to the same value as &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. Component ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; (object[]) — Optional. List of files of the module. If not provided, uses &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot; &lt;br /&gt;
        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        [files]=&amp;quot;[{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
        }]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-navbar-buttons&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to add buttons to the app&#039;s header without having to place them inside the header itself. Using this component in a site plugin will allow adding buttons to the header of the current page.&lt;br /&gt;
&lt;br /&gt;
If this component indicates a position (start/end), the buttons will only be added if the header has some buttons in that position. If no start/end is specified, then the buttons will be added to the first &amp;lt;code&amp;gt;&amp;lt;ion-buttons&amp;gt;&amp;lt;/code&amp;gt; found in the header.&lt;br /&gt;
&lt;br /&gt;
You can use the &amp;lt;code&amp;gt;[hidden]&amp;lt;/code&amp;gt; input to hide all the inner buttons if a certain condition is met.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons end&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button (click)=&amp;quot;action()&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-icon slot=&amp;quot;icon-only&amp;quot; name=&amp;quot;funnel&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can also use this to add options to the context menu, for example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons&amp;gt;&lt;br /&gt;
    &amp;lt;core-context-menu&amp;gt;&lt;br /&gt;
        &amp;lt;core-context-menu-item&lt;br /&gt;
                [priority]=&amp;quot;500&amp;quot; content=&amp;quot;Nice boat&amp;quot; (action)=&amp;quot;boatFunction()&amp;quot;&lt;br /&gt;
                iconAction=&amp;quot;boat&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/core-context-menu-item&amp;gt;&lt;br /&gt;
    &amp;lt;/core-context-menu&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Using &#039;font&#039; icons with &amp;lt;code&amp;gt;ion-icon&amp;lt;/code&amp;gt;====&lt;br /&gt;
&lt;br /&gt;
Font icons are widely used on the app and Moodle LMS website. In order to support [https://fontawesome.com/v5/search?m=free font awesome icons]. We&#039;ve added a directive that uses prefixes on the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; attribute to use different font icons.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fas-&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;fa-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=solid Font awesome solid] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;far-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=regular Font awesome regular] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fab-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=brands Font awesome brands] library (But only a few are supported and we discourage to use them).&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;moodle-&amp;lt;/code&amp;gt; will use some svg icons [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/moodle imported from Moodle LMS].&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fam-&amp;lt;/code&amp;gt; will use [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/font-awesome customized font awesome icons].&lt;br /&gt;
* If the prefix is not found or not valid, the app will search the icon name on the [https://ionic.io/ionicons ionicons library].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example of usage to show icon &amp;quot;pizza-slice&amp;quot; from font-awesome regular library:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-icon name=&amp;quot;fas-pizza-slice&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We encourage the use of font-awesome icons to match the appearance from the LMS website version.&lt;br /&gt;
&lt;br /&gt;
===Specific component and directives for plugins===&lt;br /&gt;
These are component and directives created specifically for supporting Moodle plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to display a new content when clicked. This new content can be displayed in a new page or in the current page (only if the current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The params to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in v3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in same page or open a new one. &lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that doing &amp;lt;code&amp;gt;[useOtherData]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to go to a new content page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content &lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; &lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to load new content in current page using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot; samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked. The action to do when the WS call is successful depends on the provided data: display a message, go back or refresh current view.&lt;br /&gt;
&lt;br /&gt;
If you want to load a new content when the WS call is done, please see [[#core-site-plugins-call-ws-new-content|&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The params for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;successMessage&amp;lt;/code&amp;gt; (string) — Message to show on success. If not supplied, no message. If supplied but empty, defaults to &amp;quot;Success&amp;quot;.&lt;br /&gt;
* &amp;lt;code&amp;gt;goBackOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to go back if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to refresh the current view if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to send some data to the server without using cache, displaying default messages and refreshing on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage successMessage&lt;br /&gt;
        refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to send some data to the server using cache without confirming, going back on success and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        goBackOnSuccess=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Same as the previous example, but implementing custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;certificateViewed($event)&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.certificateViewed = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked and load a new content passing the WS result as arguments. This new content can be displayed in a new page or in the same page (only if current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to load some new content when done, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The parameters to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in the same page or open a new one.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. The format is the same as in &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;jsData&amp;lt;/code&amp;gt; (any) — JS variables to pass to the new page so they can be used in the template or JS. If &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; is supplied instead of an object, all initial variables from current page will be copied. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;newContentPreSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in 3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to get some data from the server without using cache, showing default confirm and displaying a new page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage&lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to get some data from the server using cache, without confirm, displaying new content in same page and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Same as the previous example, but implementing a custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS as soon as the template is loaded. This directive is meant for actions to do in the background, like calling logging Web Services.&lt;br /&gt;
&lt;br /&gt;
If you want to call a WS when the user clicks on a certain element, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span core-site-plugins-call-ws-on-load&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Advanced features==&lt;br /&gt;
===Display the plugin only if certain conditions are met===&lt;br /&gt;
You might want to display your plugin in the mobile app only if certain dynamic conditions are met, so the plugin would be displayed only for some users. This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section ahead).&lt;br /&gt;
&lt;br /&gt;
All initialisation methods are called as soon as your plugin is retrieved. If you don&#039;t want your plugin to be displayed for the current user, then you should return the following in the initialisation method (only for Moodle site 3.8 and onwards):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;disabled&#039; =&amp;gt; true];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If the Moodle site is older than 3.8, then the initialisation method should return this instead:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;javascript&#039; =&amp;gt; &#039;this.HANDLER_DISABLED&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
On the other hand, you might want to display a plugin only for certain courses (&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;) or only if the user is viewing certain users&#039; profiles (&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;). This can be achieved with the initialisation method too.&lt;br /&gt;
&lt;br /&gt;
In the initialisation method you can return a &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; property with two fields in it: &amp;lt;code&amp;gt;courses&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;users&amp;lt;/code&amp;gt;. If you return a list of courses IDs in this property, then your plugin will only be displayed when the user views any of those courses. In the same way, if you return a list of user IDs then your plugin will only be displayed when the user views any of those users&#039; profiles.&lt;br /&gt;
===Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;===&lt;br /&gt;
The values returned by the functions in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; are added to a variable so they can be used both in JavaScript and in templates. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call is added to a variable named &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt;, while the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call is added to a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call will be passed to the JS and template of all the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; calls in that handler. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call will only be passed to the JS and template returned by that &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call.&lt;br /&gt;
&lt;br /&gt;
This means that, in your JavaScript, you can access and use the data like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.CONTENT_OTHERDATA.myVar;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the template you could use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
{{ CONTENT_OTHERDATA.myVar }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; is the name we put to one of our variables, it can be any name that you want. In the example above, this is the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
[&#039;myVar&#039; =&amp;gt; &#039;Initial value&#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Example====&lt;br /&gt;
In our plugin, we want to display an input text with a certain initial value. When the user clicks a button, we want the value in the input to be sent to a certain Web Service. This can be done using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We will return the initial value of the input in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; of our PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;myVar&#039; =&amp;gt; &#039;My initial value&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then in the template we will use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item text-wrap&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label position=&amp;quot;stacked&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.textlabel | translate }}&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;text&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.myVar&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws name=&amp;quot;mod_certificate_my_webservice&amp;quot; [useOtherDataForWS]=&amp;quot;[&#039;myVar&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we are creating an input text and we use &amp;lt;code&amp;gt;[(ngModel)]&amp;lt;/code&amp;gt; to use the value in &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; as the initial value and to store the changes in the same &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This means that the initial value of the input will be &amp;quot;My initial value&amp;quot;, and if the user changes the value of the input these changes will be applied to the &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This is called 2-way data binding in Angular.&lt;br /&gt;
&lt;br /&gt;
Then we add a button to send this data to a WS, and for that we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive. We use the &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; attribute to specify which variable from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; we want to send to our WebService. So if the user enters &amp;quot;A new value&amp;quot; in the input and then clicks the button, it will call the WebService &amp;lt;code&amp;gt;mod_certificate_my_webservice&amp;lt;/code&amp;gt; and will send as a parameter &amp;lt;code&amp;gt;[&#039;myVar&#039; =&amp;gt; &#039;A new value&#039;]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We can also achieve the same result using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; attribute of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive instead of using &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws &lt;br /&gt;
        name=&amp;quot;mod_certificate_my_webservice&amp;quot; [params]=&amp;quot;{myVar: CONTENT_OTHERDATA.myVar}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The Web Service call will be exactly the same with both versions.&lt;br /&gt;
&lt;br /&gt;
Notice that this example could be done without using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; too, using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; input of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive.&lt;br /&gt;
===Running JS code after a content template has loaded===&lt;br /&gt;
When you return JavaScript code from a handler function using the &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; array key, this code is executed immediately after the web service call returns, which may be before the returned template has been rendered into the DOM. &lt;br /&gt;
&lt;br /&gt;
If your code needs to run after the DOM has been updated, you can use &amp;lt;code&amp;gt;setTimeout&amp;lt;/code&amp;gt; to call it. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&lt;br /&gt;
    &#039;template&#039; =&amp;gt; [&lt;br /&gt;
        // ...&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;javascript&#039; =&amp;gt; &#039;setTimeout(function() { console.log(&amp;quot;DOM is available now&amp;quot;); });&#039;,&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    &#039;files&#039; =&amp;gt; [],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice that if you wanted to write a lot of code here, you might be better off putting it in a function defined in the response from an initialisation template, so that it does not get loaded again with each page of content.&lt;br /&gt;
===JS functions visible in the templates===&lt;br /&gt;
The app provides some JavaScript functions that can be used from the templates to update, refresh or view content. These are the functions:&lt;br /&gt;
* &amp;lt;code&amp;gt;openContent(title: string, args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Open a new page to display some new content. You need to specify the &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; of the new page and the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshContent(showSpinner = true)&amp;lt;/code&amp;gt; — Refresh the current content. By default, it will display a spinner while refreshing. If you don&#039;t want it to be displayed, you should pass &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; as a parameter.&lt;br /&gt;
* &amp;lt;code&amp;gt;updateContent(args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Refresh the current content using different parameters. You need to specify the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Group selector=====&lt;br /&gt;
Imagine we have an activity that uses groups and we want to let the user select which group they want to see. A possible solution would be to return all the groups in the same template (hidden), and then show the group user selects. However, we can make it more dynamic and return only the group the user is requesting.&lt;br /&gt;
&lt;br /&gt;
To do so, we&#039;ll use a drop down to select the group. When the user selects a group using this drop down, we&#039;ll update the page content to display the new group.&lt;br /&gt;
&lt;br /&gt;
The main difficulty in this is to tell the view which group needs to be selected when the view is loaded. There are 2 ways to do it: using plain HTML or using Angular&#039;s &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;.&lt;br /&gt;
======Using plain HTML======&lt;br /&gt;
We need to add a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; attribute to the option that needs to be selected. To do so, we need to pre-caclulate the selected option in the PHP code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// Detect which group is selected.&lt;br /&gt;
foreach ($groups as $gid=&amp;gt;$group) {&lt;br /&gt;
    $group-&amp;gt;selected = $gid === $groupid;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$data = [&lt;br /&gt;
    &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
    &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
    &#039;groups&#039; =&amp;gt; $groups,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the code above, we&#039;re retrieving the groups the user can see and then we&#039;re adding a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; boolean to each one to determine which one needs to be selected in the drop down. Finally, we pass the list of groups to the template.&lt;br /&gt;
&lt;br /&gt;
In the template, we display the drop down like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: $event})&amp;quot; interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot; &amp;lt;%#selected%&amp;gt;selected&amp;lt;%/selected%&amp;gt; &amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The &amp;lt;code&amp;gt;ionChange&amp;lt;/code&amp;gt; function will be called every time the user selects a different group with the drop down. We&#039;re using the &amp;lt;code&amp;gt;updateContent&amp;lt;/code&amp;gt; function to update the current view using the new group. &amp;lt;code&amp;gt;$event&amp;lt;/code&amp;gt; is an Angular variable that will have the selected value (in our case, the group ID that was just selected). This is enough to make the group selector work.&lt;br /&gt;
======Using &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;======&lt;br /&gt;
&amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; is an Angular directive that allows storing the value of a certain input or select in a JavaScript variable, and also the opposite way: tell the input or select which value to set. The main problem is that we cannot initialise a JavaScript variable from the template, so we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the PHP function we&#039;ll return the group that needs to be selected in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
        &#039;group&#039; =&amp;gt; $groupid,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we don&#039;t need to iterate over the groups array like in the plain HTML example. However, now we&#039;re returning the group id in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array. As it&#039;s explained in the [[#Using_otherdata|Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;]] section, this &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; is visible in the templates inside a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. So in the template we&#039;ll use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.group&amp;quot;&lt;br /&gt;
        (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: CONTENT_OTHERDATA.group})&amp;quot;&lt;br /&gt;
        interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Use the rich text editor===&lt;br /&gt;
The rich text editor included in the app requires a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt; to work. You can use the &amp;lt;code&amp;gt;FormBuilder&amp;lt;/code&amp;gt; library to create this control (or to create a whole &amp;lt;cide&amp;gt;FormGroup if you prefer).&lt;br /&gt;
&lt;br /&gt;
With the following JavaScript you&#039;ll be able to create a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.control = this.FormBuilder.control(this.CONTENT_OTHERDATA.rte);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we&#039;re using a value returned in &amp;lt;code&amp;gt;OTHERDATA&amp;lt;/code&amp;gt; as the initial value of the rich text editor, but you can use whatever you want.&lt;br /&gt;
&lt;br /&gt;
Then you need to pass this control to the rich text editor in your template:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;core-rich-text-editor item-content [control]=&amp;quot;control&amp;quot; placeholder=&amp;quot;Enter your text here&amp;quot; name=&amp;quot;rte_answer&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-rich-text-editor&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, there are several ways to send the value in the rich text editor to a Web Service to save it. This is one of the simplest options:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_webservice&amp;quot; [params]=&amp;quot;{rte: control.value}&amp;quot; ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re passing the value of the rich text editor as a parameter to our Web Service.&lt;br /&gt;
===Initialisation===&lt;br /&gt;
All handlers can specify an &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; method in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. This method is meant to return some JavaScript code that needs to be executed as soon as the plugin is retrieved.&lt;br /&gt;
&lt;br /&gt;
When the app retrieves all the handlers, the first thing it will do is call the &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt; Web Service with the initialisation method. This WS call will only receive the default arguments.&lt;br /&gt;
&lt;br /&gt;
The app will immediately execute the JavaScript code returned by this WS call. This JavaScript can be used to manually register your handlers in the delegates you want, without having to rely on the default handlers built based on the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; data.&lt;br /&gt;
&lt;br /&gt;
The templates returned by this method will be added to a &amp;lt;code&amp;gt;INIT_TEMPLATES&amp;lt;/code&amp;gt; variable that will be passed to all the JavaScript code of that handler. This means that the JavaScript returned by the initialisation method or the main method can access any of the templates HTML like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.INIT_TEMPLATES[&#039;main&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In this case, &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is the ID of the template we want to use.&lt;br /&gt;
&lt;br /&gt;
The same happens with the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the initialisation method, it is added to an &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt; variable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; field returned by this call will be used to determine if your handler is enabled or not. For example, if your handler is for the delegate &amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt; and you return a list of course ids in &amp;lt;code&amp;gt;restrict.courses&amp;lt;/code&amp;gt;, then your handler will only be enabled in the courses you returned. This only applies to the default handlers, if you register your own handler using the JavaScript code then you should check yourself if the handler is enabled.&lt;br /&gt;
&lt;br /&gt;
Finally, if you return an object in this initialisation JavaScript code, all the properties of that object will be passed to all the JavaScript code of that handler so you can use them when the code is run. For example, if your JavaScript code does something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    MyAddonClass: new MyAddonClass()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then, for the rest of JavaScript code of your handler (for example, the main method) you can use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.MyAddonClass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Link handlers=====&lt;br /&gt;
A link handler allows you to decide what to do when a link with a certain URL is clicked. This is useful, for example, to open your plugin page when a link to your plugin is clicked.&lt;br /&gt;
&lt;br /&gt;
After the 4.0 version, the Moodle app automatically creates two link handlers for module plugins, you don&#039;t need to create them in your plugin&#039;s Javascript code anymore:&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginanme/view.php?id=X&#039;&#039;. When this link is clicked, it will open your module in the app.&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginname/index.php?id=X&#039;&#039;. When this link is clicked, it will open a page in the app listing all the modules of your type inside a certain course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Link handlers have some advanced features that allow you to change how links behave under different conditions.&lt;br /&gt;
======Patterns======&lt;br /&gt;
You can define a Regular Expression pattern to match certain links. This will apply the handler only to links that match the pattern.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = RegExp(&#039;\/mod\/foo\/specialpage.php&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Priority======&lt;br /&gt;
Multiple link handlers may apply to a given link. You can define the order of precedence by setting the priority; the handler with the highest priority will be used.&lt;br /&gt;
&lt;br /&gt;
All default handlers have a priority of 0, so 1 or higher will override the default.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Multiple actions======&lt;br /&gt;
Once a link has been matched, the handler&#039;s &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method determines what the link should do. This method has access to the URL and its parameters.&lt;br /&gt;
&lt;br /&gt;
Different actions can be returned depending on different conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                action: function(siteId, navCtrl) {&lt;br /&gt;
                    // The actual behaviour of the link goes here.&lt;br /&gt;
                },&lt;br /&gt;
                sites: [&lt;br /&gt;
                    // ...&lt;br /&gt;
                ],&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                // ...&lt;br /&gt;
            },&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once handlers have been matched for a link, the actions will be fetched for all the matching handlers, in priorty order. The first valid action will be used to open the link.&lt;br /&gt;
&lt;br /&gt;
If your handler is matched with a link, but a condition assessed in the &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method means you want to revert to the next highest priorty handler, you can invalidate your action by settings its sites propety to an empty array.&lt;br /&gt;
======Complex example======&lt;br /&gt;
This will match all URLs containing &amp;lt;code&amp;gt;/mod/foo/&amp;lt;/code&amp;gt;, and force those with an id parameter that&#039;s not in the &amp;lt;code&amp;gt;supportedModFoos&amp;lt;/code&amp;gt; array to open in the user&#039;s browser, rather than the app.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const that = this;&lt;br /&gt;
const supportedModFoos = [...];&lt;br /&gt;
&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = new RegExp(&#039;\/mod\/foo\/&#039;);&lt;br /&gt;
        this.name = &#039;AddonModFooLinkHandler&#039;;&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {     &lt;br /&gt;
        const action = {&lt;br /&gt;
            action() {&lt;br /&gt;
                that.CoreUtilsProvider.openInBrowser(url);&lt;br /&gt;
            },&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (supportedModFoos.indexOf(parseInt(params.id)) !== -1) {&lt;br /&gt;
            action.sites = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return [action];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreContentLinksDelegate.registerHandler(new AddonModFooLinkHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Module prefetch handler=====&lt;br /&gt;
The &amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt; handler allows you to define a list of offline functions to prefetch a module. However, you might want to create your own prefetch handler to determine what needs to be downloaded. For example, you might need to chain WS calls (pass the result of a WS call to the next one), and this cannot be done using offline functions.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to create a prefetch handler using the initialisation JS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Create a class that extends from CoreCourseActivityPrefetchHandlerBase.&lt;br /&gt;
class AddonModCertificateModulePrefetchHandler extends CoreCourseActivityPrefetchHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.name = &#039;AddonModCertificateModulePrefetchHandler&#039;;&lt;br /&gt;
        this.modName = &#039;certificate&#039;;&lt;br /&gt;
&lt;br /&gt;
        // This must match the plugin identifier from db/mobile.php,&lt;br /&gt;
        // otherwise the download link in the context menu will not update correctly.&lt;br /&gt;
        this.component = &#039;mod_certificate&#039;;&lt;br /&gt;
        this.updatesNames = /^configuration$|^.*files$/;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Override the prefetch call.&lt;br /&gt;
    prefetch(module, courseId, single, dirPath) {&lt;br /&gt;
        return this.prefetchPackage(module, courseId, single, prefetchCertificate);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prefetchCertificate(module, courseId, single, siteId) {&lt;br /&gt;
    // Perform all the WS calls.&lt;br /&gt;
    // You can access most of the app providers using that.ClassName. E.g. that.CoreWSProvider.call().&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseModulePrefetchDelegate.registerHandler(new AddonModCertificateModulePrefetchHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
One relatively simple full example is where you have a function that needs to work offline, but it has an additional argument other than the standard ones. You can imagine for this an activity like the book module, where it has multiple pages for the same &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;. The app will not automatically work with this situation — it will call the offline function with the standard arguments only — so you won&#039;t be able to prefetch all the possible parameters.&lt;br /&gt;
&lt;br /&gt;
To deal with this, you need to implement a web service in your Moodle component that returns the list of possible extra arguments, and then you can call this web service and loop around doing the same thing the app does when it prefetches the offline functions. Here is an example from a third-party module (showing only the actual prefetch function, the rest of the code is as above) where there are multiple values of a custom &amp;lt;code&amp;gt;section&amp;lt;/code&amp;gt; parameter for the mobile function &amp;lt;code&amp;gt;mobile_document_view&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function prefetchOucontent(module, courseId, single, siteId) {&lt;br /&gt;
    var component = &#039;mod_oucontent&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Get the site, first.&lt;br /&gt;
    return that.CoreSitesProvider.getSite(siteId).then(function(site) {&lt;br /&gt;
        // Read the list of pages in this document using a web service.&lt;br /&gt;
        return site.read(&#039;mod_oucontent_get_page_list&#039;, {&#039;cmid&#039;: module.id}).then(function(response) {&lt;br /&gt;
            var promises = [];&lt;br /&gt;
&lt;br /&gt;
            // For each page, read and process the page - this is a copy of logic in the app at&lt;br /&gt;
            // siteplugins.ts (prefetchFunctions), but modified to add the custom argument.&lt;br /&gt;
            for(var i = 0; i &amp;lt; response.length; i++) {&lt;br /&gt;
                var args = {&lt;br /&gt;
                    courseid: courseId,&lt;br /&gt;
                    cmid: module.id,&lt;br /&gt;
                    userid: site.getUserId()&lt;br /&gt;
                };&lt;br /&gt;
                if (response[i] !== &#039;&#039;) {&lt;br /&gt;
                    args.section = response[i];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                promises.push(that.CoreSitePluginsProvider.getContent(&lt;br /&gt;
                        component, &#039;mobile_document_view&#039;, args).then(&lt;br /&gt;
                        function(result) {&lt;br /&gt;
                            var subPromises = [];&lt;br /&gt;
                            if (result.files &amp;amp;&amp;amp; result.files.length) {&lt;br /&gt;
                                subPromises.push(that.CoreFilepoolProvider.downloadOrPrefetchFiles(&lt;br /&gt;
                                        site.id, result.files, true, false, component, module.id));&lt;br /&gt;
                            }&lt;br /&gt;
                            return Promise.all(subPromises);&lt;br /&gt;
                        }));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(promises);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Single activity course format=====&lt;br /&gt;
In the following example, the value of &amp;lt;code&amp;gt;INIT_TEMPLATES[&#039;main&#039;]&amp;lt;/code&amp;gt; is:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;componentClass&amp;quot; [data]=&amp;quot;data&amp;quot;&amp;gt;&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This template is returned by the initialisation method. And this is the JavaScript code returned:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatComponent {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.data = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ngOnChanges(changes) {&lt;br /&gt;
        var self = this;&lt;br /&gt;
&lt;br /&gt;
        if (this.course &amp;amp;&amp;amp; this.sections &amp;amp;&amp;amp; this.sections.length) {&lt;br /&gt;
            var module = this.sections[0] &amp;amp;&amp;amp; this.sections[0].modules &amp;amp;&amp;amp; this.sections[0].modules[0];&lt;br /&gt;
            if (module &amp;amp;&amp;amp; !this.componentClass) {&lt;br /&gt;
                that.CoreCourseModuleDelegate.getMainComponent(that.Injector, this.course, module).then((component) =&amp;gt; {&lt;br /&gt;
                    self.componentClass = component || that.CoreCourseUnsupportedModuleComponent;&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.data.courseId = this.course.id;&lt;br /&gt;
            this.data.module = module;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    doRefresh(refresher, done) {&lt;br /&gt;
        return Promise.resolve(this.dynamicComponent.callComponentFunction(&amp;quot;doRefresh&amp;quot;, [refresher, done]));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatHandler {&lt;br /&gt;
    &lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.name = &#039;singleactivity&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    isEnabled() {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canViewAllSections() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseTitle(course, sections) {&lt;br /&gt;
        if (sections &amp;amp;&amp;amp; sections[0] &amp;amp;&amp;amp; sections[0].modules &amp;amp;&amp;amp; sections[0].modules[0]) {&lt;br /&gt;
            return sections[0].modules[0].name;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return course.fullname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displayEnableDownload() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displaySectionSelector() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseFormatComponent() {&lt;br /&gt;
        return that.CoreCompileProvider.instantiateDynamicComponent(that.INIT_TEMPLATES[&#039;main&#039;], AddonSingleActivityFormatComponent);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseFormatDelegate.registerHandler(new AddonSingleActivityFormatHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using the JavaScript API===&lt;br /&gt;
The JavaScript API is only supported by the delegates specified in the [[#Templates_downloaded_on_login_and_rendered_using_JS_data_2|Templates downloaded on login and rendered using JS data]] section. This API allows you to override any of the functions of the default handler.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; specified in a handler registered in the &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; will be called immediately after the initialisation method, and the JavaScript returned by this method will be run. If this JavaScript code returns an object with certain functions, these functions will override the ones in the default handler.&lt;br /&gt;
&lt;br /&gt;
For example, if the JavaScript returned by the method returns something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The the &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function of the default handler will be overridden by the returned &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
The default handler for &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; only has 2 functions: &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt;. In addition, the JavaScript code can return an extra function named &amp;lt;code&amp;gt;componentInit&amp;lt;/code&amp;gt; that will be executed when the component returned by &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; is initialised.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to support the text user profile field using this API:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
var result = {&lt;br /&gt;
    componentInit: function() {&lt;br /&gt;
        if (this.field &amp;amp;&amp;amp; this.edit &amp;amp;&amp;amp; this.form) {&lt;br /&gt;
            this.field.modelName = &#039;profile_field_&#039; + this.field.shortname;&lt;br /&gt;
&lt;br /&gt;
            if (this.field.param2) {&lt;br /&gt;
                this.field.maxlength = parseInt(this.field.param2, 10) || &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.field.inputType = that.CoreUtilsProvider.isTrueOrOne(this.field.param3) ? &#039;password&#039; : &#039;text&#039;;&lt;br /&gt;
&lt;br /&gt;
            var formData = {&lt;br /&gt;
                value: this.field.defaultdata,&lt;br /&gt;
                disabled: this.disabled,&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            this.form.addControl(this.field.modelName,&lt;br /&gt;
                that.FormBuilder.control(formData, this.field.required &amp;amp;&amp;amp; !this.field.locked ? that.Validators.required : null));&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        var name = &#039;profile_field_&#039; + field.shortname;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            type: &amp;quot;text&amp;quot;,&lt;br /&gt;
            name: name,&lt;br /&gt;
            value: that.CoreTextUtilsProvider.cleanTags(formValues[name]),&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Translate dynamic strings===&lt;br /&gt;
If you wish to have an element that displays a localised string based on value from your template you can doing something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_myactivity.&amp;lt;% status %&amp;gt;&#039; | translate }}&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This could save you from having to write something like when only one value should be displayed:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        &amp;lt;%#isedting%&amp;gt;{{ &#039;plugin.mod_myactivity.editing&#039; | translate }}&amp;lt;%/isediting%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isopen%&amp;gt;{{ &#039;plugin.mod_myactivity.open&#039; | translate }}&amp;lt;%/isopen%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isclosed%&amp;gt;{{ &#039;plugin.mod_myactivity.closed&#039; | translate }}&amp;lt;%/isclosed%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using strings with dates===&lt;br /&gt;
If you have a string that you wish to pass a formatted date, for example in the Moodle language file you have:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;strwithdate&#039;] = &#039;This string includes a date of {$a-&amp;gt;date} in the middle of it.&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can localise the string correctly in your template using something like the following:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{ &#039;plugin.mod_myactivity.strwithdate&#039; | translate: {$a: { date: &amp;lt;% timestamp %&amp;gt; * 1000 | coreFormatDate: &amp;quot;dffulldate&amp;quot; } } }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A Unix timestamp must be multiplied by 1000 as the Mobile App expects millisecond timestamps, whereas Unix timestamps are in seconds.&lt;br /&gt;
===Support push notification clicks===&lt;br /&gt;
If your plugin sends push notifications to the app, you might want to open a certain page in the app when the notification is clicked. There are several ways to achieve this.&lt;br /&gt;
&lt;br /&gt;
The easiest way is to include a &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; in your notification. When the notification is clicked, the app will try to open the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; will also be displayed in web. If you want to use a specific URL for the app, different than the one displayed in web, you can do so by returning a &amp;lt;code&amp;gt;customdata&amp;lt;/code&amp;gt; array that contains an &amp;lt;code&amp;gt;appurl&amp;lt;/code&amp;gt; property:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$notification-&amp;gt;customdata = [&lt;br /&gt;
    &#039;appurl&#039; =&amp;gt; $myurl-&amp;gt;out(),&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In both cases you will have to create a link handler to treat the URL. For more info on how to create the link handler, please see [[#Advanced_link_handler|how to create an advanced link handler]].&lt;br /&gt;
&lt;br /&gt;
If you want to do something that only happens when the notification is clicked, not when the link is clicked, you&#039;ll have to implement a push click handler yourself. The way to create it is similar to [[#Advanced_link_handler|creating an advanced link handler]], but you&#039;ll have to use &amp;lt;code&amp;gt;CorePushNotificationsDelegate&amp;lt;/code&amp;gt; and your handler will have to implement the properties and functions defined in the [https://github.com/moodlehq/moodleapp/blob/master/src/core/features/pushnotifications/services/push-delegate.ts#L27 CorePushNotificationsClickHandler] interface.&lt;br /&gt;
===Implement a module similar to mod_label===&lt;br /&gt;
In Moodle 3.8 or higher, if your plugin doesn&#039;t support &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt; and you don&#039;t specify a &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; then the module will only display the module description in the course page and it won&#039;t be clickable in the app, just like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt;. You can decide if you want the module icon to be displayed or not (if you don&#039;t want it to be displayed, then don&#039;t define it in &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
However, if your plugin needs to work in previous versions of Moodle or you want to display something different than the description then you need a different approach.&lt;br /&gt;
&lt;br /&gt;
If your plugin wants to render something in the course page instead of just the module name and description you should specify the &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. The template returned by this method will be rendered in the course page. Please notice the HTML returned should not contain directives or components, only plain HTML.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want your module to be clickable then you just need to remove &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. With these 2 changes you can have a module that behaves like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt; in the app.&lt;br /&gt;
===Use Ionic navigation lifecycle functions===&lt;br /&gt;
Ionic let pages define some functions that will be called when certain navigation lifecycle events happen. For more info about these functions, see [https://ionicframework.com/docs/api/router-outlet Ionic&#039;s documentation].&lt;br /&gt;
&lt;br /&gt;
You can define these functions in your plugin javascript:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.ionViewWillLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In addition to that, you can also implement &amp;lt;code&amp;gt;canLeave&amp;lt;/code&amp;gt; to use Angular route guards:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.canLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So for example you can make your plugin ask for confirmation if the user tries to leave the page when he has some unsaved data.&lt;br /&gt;
===Module plugins: dynamically determine if a feature is supported===&lt;br /&gt;
In Moodle you can specify if your plugin supports a certain feature, like &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. If your plugin will always support or not a certain feature, then you can use the &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; to specify it ([[#Options_only_for_CoreCourseModuleDelegate|see more documentation about this]]). But if you need to calculate it dynamically then you will have to create a function to calculate it. &lt;br /&gt;
&lt;br /&gt;
This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section above). The JavaScript returned by your initialisation method will need to define a function named &amp;lt;code&amp;gt;supportsFeature&amp;lt;/code&amp;gt; that will receive the name of the feature:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    supportsFeature: function(featureName) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;.&lt;br /&gt;
== Testing ==&lt;br /&gt;
You can also write automated tests for your plugin using Behat, you can read more about it on the [[Acceptance testing for the Moodle App]] page.&lt;br /&gt;
== Upgrading plugins from an older version ==&lt;br /&gt;
If you added mobile support to your plugin for the Ionic 3 version of the app (previous to the 3.9.5 release), you will probably need to make some changes to make it compatible with Ionic 5.&lt;br /&gt;
&lt;br /&gt;
Learn more at the [[Moodle App Plugins Upgrade Guide]].&lt;br /&gt;
==Troubleshooting==&lt;br /&gt;
=== Invalid response received ===&lt;br /&gt;
You might receive this error when using the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive or similar. By default, the app expects all Web Service calls to return an object, if your Web Service returns another type (string, boolean, etc.) then you need to specify it using the &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; attribute of the directive. For example, if your WS returns a boolean value, then you should specify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{typeExpected: &#039;boolean&#039;}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In a similar way, if your Web Service returns &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; you need to tell the app not to expect any result using &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{responseExpected: false}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS ===&lt;br /&gt;
Some directives allow you to specify a form id or name to send the data from the form to a certain WS. These directives look for HTML inputs to retrieve the data to send. However, &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; don&#039;t use HTML inputs, they simulate them, so the directive isn&#039;t going to find their data and so it won&#039;t be sent to the Web Service.&lt;br /&gt;
&lt;br /&gt;
There are 2 workarounds to fix this problem.&lt;br /&gt;
==== Sending the data manually ====&lt;br /&gt;
The first solution is to send the missing params manually using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; property. We will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input value in a variable, and this variable will be passed to the parameters. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a template like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group name=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, responses: responses}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Basically, you need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to the affected element (in this case, the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt;). You can put whatever name you want as the value, we used &amp;quot;responses&amp;quot;. With this, every time the user selects a radio button the value will be stored in a variable called &amp;quot;responses&amp;quot;. Then, in the button we are passing this variable to the parameters of the Web Service.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute has priority over &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;, so if you have an input with &amp;lt;code&amp;gt;name=&amp;quot;responses&amp;quot;&amp;lt;/code&amp;gt; it will override what you&#039;re manually passing to &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;.&lt;br /&gt;
==== Using a hidden input ====&lt;br /&gt;
Since the directive is looking for HTML inputs, you need to add one with the value to send to the server. You can use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to synchronise your radio/checkbox/select with the new hidden input. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a radio button like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot; [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;hidden&amp;quot; [ngModel]=&amp;quot;responses&amp;quot; name=&amp;quot;responses&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we&#039;re using a variable called &amp;quot;responses&amp;quot; to synchronise the data between the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt; and the hidden input. You can use whatever name you want.&lt;br /&gt;
=== I can&#039;t return an object or array in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; ===&lt;br /&gt;
If you try to return an object or an array in any field inside &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, the Web Service call will fail with the following error:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Scalar type expected, array or object received&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Each field in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; must be a string, number or boolean; it cannot be an object or array. To make it work, you need to encode your object or array into a JSON string:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($data)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app will automatically parse this JSON and convert it back into an array or object.&lt;br /&gt;
==Examples==&lt;br /&gt;
===Accepting dynamic names in a Web Service===&lt;br /&gt;
We want to display a form where the names of the fields are dynamic, like it happens in quiz. This data will be sent to a new Web Service that we have created.&lt;br /&gt;
&lt;br /&gt;
The first issue we find is that the Web Service needs to define the names of the parameters received, but in this case they&#039;re dynamic. The solution is to accept an array of objects with name and value. So in the &amp;lt;code&amp;gt;_parameters()&amp;lt;/code&amp;gt; function of our new Web Service, we will add this parameter:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;data&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
     new external_single_structure(&lt;br /&gt;
        [&lt;br /&gt;
            &#039;name&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data name&#039;),&lt;br /&gt;
            &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data value&#039;),&lt;br /&gt;
        ]&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;The data to be saved&#039;, VALUE_DEFAULT, []&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now we need to adapt our form to send the data as the Web Service requires it. In our template, we have a button with the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive that will send the form data to our Web Service. To make this work we will have to pass the parameters manually, without using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute, because we need to format the data before it is sent.&lt;br /&gt;
&lt;br /&gt;
Since we will send the parameters manually and we want it all to be sent in the same array, we will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input data into a variable that we&#039;ll call &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;, but you can use the name you want. This variable will be an object that will hold the input data with the format &amp;quot;name-&amp;gt;value&amp;quot;. For example, if I have an input with name &amp;quot;a1&amp;quot; and value &amp;quot;My answer&amp;quot;, the data object will be:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{a1: &#039;My answer&#039;}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So we need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to all the inputs whose values need to be sent to the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; WS param. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-input name=&amp;quot;&amp;lt;% name %&amp;gt;&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.data[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re using &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; to store the data. We do it like this because we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the form, setting the values the user has already stored. If you don&#039;t need to initialise the form, then you can use the &amp;lt;code&amp;gt;dataObject&amp;lt;/code&amp;gt; variable, an empty object that the mobile app creates for you:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[(ngModel)]=&amp;quot;dataObject[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app has a function that allows you to convert this data object into an array like the one the WS expects: &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt;. So in our button we&#039;ll use this function to format the data before it&#039;s sent:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_ws_name&amp;quot;&lt;br /&gt;
    [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, data: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.data, &#039;name&#039;, &#039;value&#039;)}&amp;quot;&lt;br /&gt;
    successMessage&lt;br /&gt;
    refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see in the example above, we&#039;re specifying that the keys of the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object need to be stored in a property called &amp;quot;name&amp;quot;, and the values need to be stored in a property called &amp;quot;value&amp;quot;. If your Web Service expects different names you need to change the parameters of the &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
If you open your plugin now in the app it will display an error in the JavaScript console. The reason is that the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; variable doesn&#039;t exist inside &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. As it is explained in previous sections, &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; holds the data that you return in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; for your method. We&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the values to be displayed in the form.&lt;br /&gt;
&lt;br /&gt;
If the user hasn&#039;t answered the form yet, we can initialise the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object as an empty object. Please remember that we cannot return arrays or objects in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, so we&#039;ll return a JSON string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; &#039;{}&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
With the code above, the form will always be empty when the user opens it. But now we want to check if the user has already answered the form and fill the form with the previous values. We will do it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$userdata = get_user_responses(); // It will held the data in a format name-&amp;gt;value. Example: [&#039;a1&#039; =&amp;gt; &#039;My value&#039;].&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($userdata)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now the user will be able to see previous values when the form is opened, and clicking the button will send the data to our Web Service in array format.&lt;br /&gt;
==Moodle plugins with mobile support==&lt;br /&gt;
* Group choice: [https://moodle.org/plugins/mod_choicegroup Moodle plugins directory entry] and [https://github.com/ndunand/moodle-mod_choicegroup code in github].&lt;br /&gt;
* Custom certificate: [https://moodle.org/plugins/mod_customcert Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_customcert code in github].&lt;br /&gt;
* Gapfill question type: [https://moodle.org/plugins/qtype_gapfill Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_gapfill in github].&lt;br /&gt;
* Wordselect question type: [https://moodle.org/plugins/qtype_wordselect Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_wordselect in github].&lt;br /&gt;
* RegExp question type: [https://moodle.org/plugins/qtype_regexp Moodle plugins directory entry] and [https://github.com/rezeau/moodle-qtype_regexp in github].&lt;br /&gt;
* Certificate: [https://moodle.org/plugins/mod_certificate Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_certificate in github].&lt;br /&gt;
* Attendance [https://moodle.org/plugins/mod_attendance Moodle plugins directory entry] and [https://github.com/danmarsden/moodle-mod_attendance in github].&lt;br /&gt;
* ForumNG (unfinished support) [https://moodle.org/plugins/mod_forumng Moodle plugins directory entry] and [https://github.com/moodleou/moodle-mod_forumng in github].&lt;br /&gt;
* News block [https://github.com/moodleou/moodle-block_news in github].&lt;br /&gt;
* H5P activity module [https://moodle.org/plugins/mod_hvp Moodle plugins directory entry] and [https://github.com/h5p/h5p-moodle-plugin in github].&lt;br /&gt;
See the complete list in [https://moodle.org/plugins/browse.php?list=award&amp;amp;id=6 the plugins database] (it may contain some outdated plugins).&lt;br /&gt;
=== Mobile app support award ===&lt;br /&gt;
If you want your plugin to be awarded in the plugins directory and marked as supporting the mobile app, please feel encouraged to contact us via email at [mailto:mobile@moodle.com mobile@moodle.com].&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to include a link to your plugin page and the location of its code repository.&lt;br /&gt;
&lt;br /&gt;
See [https://moodle.org/plugins/?q=award:mobile-app the list of awarded plugins] in the plugins directory.&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Moodle App Ionic 5]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62057</id>
		<title>Moodle App Plugins Development Guide</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62057"/>
		<updated>2022-04-25T08:18:21Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: /* Step 4. Adding an additional page */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle App (Ionic 5)}}&lt;br /&gt;
If you want to add mobile support to your Moodle plugin, you can achieve it by extending different areas of the app using &#039;&#039;just PHP server side code&#039;&#039; and providing templates written with [https://ionicframework.com/docs/components Ionic] and custom components.&lt;br /&gt;
&lt;br /&gt;
You will have to:&lt;br /&gt;
# Create a &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file in your plugin. In this file, you will be able to indicate which areas of the app you want to extend. For example, adding a new option in the main menu, implementing support for a new activity module, including a new option in the course menu, including a new option in the user profile, etc. All the areas supported are described further in this document.&lt;br /&gt;
# Create new functions in a reserved namespace that will return the content of the new options. The content should be returned rendered as html. This html should use Ionic components so that it looks native, but it can be generated using mustache templates.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let’s clarify some points:&lt;br /&gt;
* You don’t need to create new Web Service functions (although you will be able to use them for advanced features). You just need plain php functions that will be placed in a reserved namespace.&lt;br /&gt;
* Those functions will be exported via the Web Service function &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt;.&lt;br /&gt;
* As arguments of your functions you will always receive the &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, some relevant details of the app (like the app version or the current language in the app), and some specific data depending on the type of plugin (&amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
* The mobile app also implements a list of custom Ionic components and directives that provide dynamic behaviour; like indicating that you are linking a file that can be downloaded, allowing a transition to new pages into the app calling a specific function in the server, submitting form data to the server, etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Getting started==&lt;br /&gt;
If you only want to write a plugin, it is not necessary that you set up your environment to work with the Moodle App. In fact, you don&#039;t even need to compile it. You can just [[Using the Moodle App in a browser|use a Chromium-based browser]] to add mobile support to your plugins!&lt;br /&gt;
&lt;br /&gt;
You can use the app from one of the hosted versions on [https://master.apps.moodledemo.net master.apps.moodledemo.net] (the latest stable version) and [https://integration.apps.moodledemo.net integration.apps.moodledemo.net] (the latest development version). If you need any specific environment (hosted versions are deployed with a &#039;&#039;production&#039;&#039; environment), you can also use [[Moodle App Docker Images|Docker images]]. And if you need to test your plugin in a native device, you can always use [https://download.moodle.org/mobile Moodle HQ&#039;s application].&lt;br /&gt;
&lt;br /&gt;
This should suffice for developing plugins. However, if you are working on advanced functionality and you need to run the application from the source code, you can find more information in the [[Moodle App Development Guide]].&lt;br /&gt;
===Development workflow===&lt;br /&gt;
Before getting into the specifics of your plugin, we recommend that you start adding a simple &amp;quot;Hello World&amp;quot; button in the app to see that everything works properly.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s say your plugin is called &amp;lt;code&amp;gt;local_hello&amp;lt;/code&amp;gt;, you can start by adding the following files:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;local_hello&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [&lt;br /&gt;
            &#039;hello&#039; =&amp;gt; [&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreMainMenuDelegate&#039;,&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;view_hello&#039;,&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;title&#039; =&amp;gt; &#039;hello&#039;,&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; &#039;earth&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [&lt;br /&gt;
            [&#039;hello&#039;, &#039;local_hello&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace local_hello\output;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    public static function view_hello() {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; &#039;&amp;lt;h1 class=&amp;quot;text-center&amp;quot;&amp;gt;{{ &amp;quot;plugin.local_hello.hello&amp;quot; | translate }}&amp;lt;/h1&amp;gt;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;lang/en/local_hello.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;hello&#039;] = &#039;Hello World&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once you&#039;ve done that, try logging into your site in the app and you should see a new button in the main menu or more menu (depending on the device) saying &amp;quot;Hello World&amp;quot;. If you press this button, you should see a page saying &amp;quot;Hello World!&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Congratualtions, you have written your first Moodle plugin with moodle support!&lt;br /&gt;
&lt;br /&gt;
You can read the rest of this page to learn more about mobile plugins and start working on your plugin. Here&#039;s some things to keep in mind:&lt;br /&gt;
* If you change the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file, you will have to refresh the browser. And remember to [https://developer.chrome.com/docs/devtools/network/reference/#disable-cache disable the network cache].&lt;br /&gt;
* If you change an existing template or function, you won’t have to refresh the browser. In most cases, doing a PTR (Pull To Refresh) in the page that displays the template will suffice.&lt;br /&gt;
* If any of these doesn&#039;t show your changes, you may need to [https://docs.moodle.org/311/en/Developer_tools#Purge_all_caches purge all caches] to avoid problems with the auto-loading cache.&lt;br /&gt;
* Ultimately, if that doesn&#039;t work either, you may have to log out from the site and log in again. If any changes affect plugin installation, you may also need to increase the version in your plugin&#039;s &amp;lt;code&amp;gt;version.php&amp;lt;/code&amp;gt; file and upgrade it in the site.&lt;br /&gt;
==Types of plugins==&lt;br /&gt;
There are 3 types of plugins:&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
[[File:Templates_downloaded_when_requested.png|thumb]]&lt;br /&gt;
With this type of plugin, the template of your plugin will be generated and downloaded when the user opens the plugin in the app. This means that your function will receive some context parameters. For example, if you&#039;re developing a course module plugin you will receive the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; (course module ID). You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
[[File:Templates_downloaded_on_login.png|thumb]]&lt;br /&gt;
With this type of plugin, the template for your plugin will be downloaded when the user logs in into the app and will be stored in the device. This means that your function will not receive any context parameters, and you need to return a generic template that will be built with JS data like the ones in the Moodle App. When the user opens a page that includes your plugin, your template will receive the required JS data and your template will be rendered. You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
You can always implement the whole plugin yourself using JavaScript instead of using our API. In fact, this is required if you want to implement some features like capturing links in the Mobile app. You can see the list of delegates that only support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
==Step by step example==&lt;br /&gt;
In this example, we are going to update an existing plugin, the [https://github.com/mdjnelson/moodle-mod_certificate Certificate activity module], that previously used a [[Moodle Mobile 2 (Ionic 1) Remote add-ons|Remote add-on]] (a legacy approach to implement mobile plugins).&lt;br /&gt;
&lt;br /&gt;
This is a simple activity module that displays the certificate issued for the current user along with the list of the dates of previously issued certificates. It also stores in the course log that the user viewed a certificate. This module also works offline: when the user downloads the course or activity, the data is pre-fetched and can be viewed offline.&lt;br /&gt;
===Step 1. Update the &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file===&lt;br /&gt;
In this case, we are updating an existing file. For new plugins, you should create this new file.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;mod_certificate&#039; =&amp;gt; [ // Plugin identifier&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;coursecertificate&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/mod/certificate/pix/icon.gif&#039;,&lt;br /&gt;
                    &#039;class&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
       &lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseModuleDelegate&#039;, // Delegate (where to display the link to the plugin)&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;mobile_course_view&#039;, // Main function in \mod_certificate\output\mobile&lt;br /&gt;
                &#039;offlinefunctions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;mobile_course_view&#039; =&amp;gt; [],&lt;br /&gt;
                    &#039;mobile_issues_view&#039; =&amp;gt; [],&lt;br /&gt;
                ], // Function that needs to be downloaded for offline.&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [ // Language strings that are used in all the handlers.&lt;br /&gt;
            [&#039;pluginname&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;summaryofattempts&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;getcertificate&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;requiredtimenotmet&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;viewcertificateviews&#039;, &#039;certificate&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Plugin identifier:&lt;br /&gt;
: A unique name for the plugin, it can be anything (there’s no need to match the module name).&lt;br /&gt;
&lt;br /&gt;
;Handlers (Different places where the plugin will display content):&lt;br /&gt;
: A plugin can be displayed in different views in the app. Each view should have a unique name inside the plugin scope (alphanumeric).&lt;br /&gt;
&lt;br /&gt;
; Display data:&lt;br /&gt;
: This is only needed for certain types of plugins. Also, depending on the type of delegate it may require additional (or less fields). In this case, we are indicating the module icon.&lt;br /&gt;
&lt;br /&gt;
; Delegate&lt;br /&gt;
: Where to display the link to the plugin, see the [[#Delegates|Delegates]] section for all the possible options.&lt;br /&gt;
&lt;br /&gt;
; Method:&lt;br /&gt;
: This is the method in the Moodle &amp;lt;code&amp;gt;\{component-name}\output\mobile&amp;lt;/code&amp;gt; class to be executed the first time the user clicks in the new option displayed in the app.&lt;br /&gt;
&lt;br /&gt;
; Offlinefunctions&lt;br /&gt;
: This is the list of functions that need to be called and stored when the user downloads a course for offline usage. Please note that you can add functions here that are not even listed in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. &lt;br /&gt;
: In our example, downloading for offline access will mean that we&#039;ll execute the functions for getting the certificate and issued certificates passing as parameters the current &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; (and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; when we are using the mod or course delegate). If we have the result of those functions stored in the app, we&#039;ll be able to display the certificate information even if the user is offline.&lt;br /&gt;
: Offline functions will be mostly used to display information for final users, any further interaction with the view won’t be supported offline (for example, trying to send information when the user is offline).&lt;br /&gt;
: You can indicate here other Web Services functions, indicating the parameters that they might need from a defined subset (currently &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Prefetching the module will also download all the files returned by the methods in these offline functions (in the &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; array).&lt;br /&gt;
: Note that if your functions use additional custom parameters (for example, if you implement multiple pages within a module&#039;s view function by using a &amp;lt;code&amp;gt;page&amp;lt;/code&amp;gt; parameter in addition to the usual &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;) then the app will not know which additional parameters to supply. In this case, do not list the function in &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt;; instead, you will need to manually implement a [[#Module_prefetch_handler|module prefetch handler]].&lt;br /&gt;
&lt;br /&gt;
;Lang:&lt;br /&gt;
: The language pack string ids used in the plugin by all the handlers. Normally these will be strings from your own plugin, however, you can list any strings you need here, like &amp;lt;code&amp;gt;[&#039;cancel&#039;, &#039;moodle&#039;]&amp;lt;/code&amp;gt;. If you do this, be warned that in the app you will then need to refer to that string as &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.myplugin.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (not &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.moodle.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Please only include the strings you actually need. The Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform, and this will then be cached, so listing too many strings is very wasteful.&lt;br /&gt;
There are additional attributes supported by the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; list, you can find about them in the [[#Mobile.php_supported_options|Mobile.php supported options]] section.&lt;br /&gt;
===Step 2. Creating the main function===&lt;br /&gt;
The main function displays the current issued certificate (or several warnings if it’s not possible to issue a certificate). It also displays a link to view the dates of previously issued certificates.&lt;br /&gt;
&lt;br /&gt;
All the functions must be created in the plugin or subsystem &amp;lt;code&amp;gt;classes/output&amp;lt;/code&amp;gt; directory, the name of the class must be &amp;lt;code&amp;gt;mobile&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
For this example, the namespace name will be &amp;lt;code&amp;gt;mod_certificate\output&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace mod_certificate\output;&lt;br /&gt;
&lt;br /&gt;
use context_module;&lt;br /&gt;
use mod_certificate_external;&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Returns the certificate course view for the mobile app.&lt;br /&gt;
     *&lt;br /&gt;
     * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
     *&lt;br /&gt;
     * @return array       HTML, JS and other data.&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
        $args = (object) $args;&lt;br /&gt;
        $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
        // Capabilities check.&lt;br /&gt;
        require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
        $context = \context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
        require_capability(&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
        if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
            require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
        }&lt;br /&gt;
        $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
        // Get certificates from external (taking care of exceptions).&lt;br /&gt;
        try {&lt;br /&gt;
            $issued = \mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
            $certificates = \mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
            $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $issues = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Set timemodified for each certificate.&lt;br /&gt;
        foreach ($issues as $issue) {&lt;br /&gt;
            if (empty($issue-&amp;gt;timemodified)) {&lt;br /&gt;
                $issue-&amp;gt;timemodified = $issue-&amp;gt;timecreated;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $showget = true;&lt;br /&gt;
        if ($certificate-&amp;gt;requiredtime &amp;amp;&amp;amp; !has_capability(&#039;mod/certificate:manage&#039;, $context)) {&lt;br /&gt;
            if (certificate_get_course_time($certificate-&amp;gt;course) &amp;lt; ($certificate-&amp;gt;requiredtime * 60)) {&lt;br /&gt;
                $showget = false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $certificate-&amp;gt;name = format_string($certificate-&amp;gt;name);&lt;br /&gt;
        [$certificate-&amp;gt;intro, $certificate-&amp;gt;introformat] =&lt;br /&gt;
                external_format_text($certificate-&amp;gt;intro, $certificate-&amp;gt;introformat, $context-&amp;gt;id, &#039;mod_certificate&#039;, &#039;intro&#039;);&lt;br /&gt;
        $data = [&lt;br /&gt;
            &#039;certificate&#039; =&amp;gt; $certificate,&lt;br /&gt;
            &#039;showget&#039; =&amp;gt; $showget &amp;amp;&amp;amp; count($issues) &amp;gt; 0,&lt;br /&gt;
            &#039;issues&#039; =&amp;gt; $issues,&lt;br /&gt;
            &#039;issue&#039; =&amp;gt; $issues[0],&lt;br /&gt;
            &#039;numissues&#039; =&amp;gt; count($issues),&lt;br /&gt;
            &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
            &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;files&#039; =&amp;gt; $issues,&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Function declaration: &lt;br /&gt;
: The function name is the same as the one used in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file (&amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; field). There is only one argument, &amp;lt;code&amp;gt;$args&amp;lt;/code&amp;gt;, which is an array containing all the information sent by the mobile app (the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversionname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversioncode&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;applang&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appcustomurlscheme&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
&lt;br /&gt;
; Function implementation:&lt;br /&gt;
: In the first part of the function, we check permissions and capabilities (like a &amp;lt;code&amp;gt;view.php&amp;lt;/code&amp;gt; script would do normally). Then we retrieve the certificate information that’s necessary to display the template.&lt;br /&gt;
&lt;br /&gt;
; Function return:&lt;br /&gt;
* &amp;lt;code&amp;gt;templates&amp;lt;/code&amp;gt; — The rendered template (notice that we could return more than one template, but we usually would only need one). By default the app will always render the first template received, the rest of the templates can be used if the plugin defines some JavaScript code.&lt;br /&gt;
* &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; — Empty, because we don’t need any in this case.&lt;br /&gt;
* &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; — Empty as well, because we don’t need any additional data to be used by directives or components in the template. This field will be published as an object supporting 2-way data-binding in the template.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; — A list of files that the app should be able to download (for offline usage mostly).&lt;br /&gt;
===Step 3. Creating the template for the main function===&lt;br /&gt;
This is the most important part of your plugin because it contains the code that will be rendered on the mobile app.&lt;br /&gt;
&lt;br /&gt;
In this template we’ll be using Ionic, together with directives and components specific to the Moodle App.&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;ion-&amp;lt;/code&amp;gt; are ionic components. Most of the time, the component name is self-explanatory but you may refer to a detailed guide here: https://ionicframework.com/docs/components/&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;core-&amp;lt;/code&amp;gt; are custom components of the Moodle App.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_page.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;core-course-module-description description=&amp;quot;&amp;lt;% certificate.intro %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-course-module-description&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;ion-list-header&amp;gt;&lt;br /&gt;
            &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.summaryofattempts&#039; | translate }}&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-list-header&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-new-content title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; &lt;br /&gt;
                        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
                        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
                    {{ &#039;plugin.mod_certificate.viewcertificateviews&#039; | translate: {$a: &amp;lt;% numissues %&amp;gt;} }}&lt;br /&gt;
                &amp;lt;/ion-button&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&lt;br /&gt;
                        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
                        [files]=&amp;quot;[{&lt;br /&gt;
                            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
                            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
                            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
                        }]&amp;quot;&amp;gt;&lt;br /&gt;
                 &lt;br /&gt;
                        &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot; slot=&amp;quot;start&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                            {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
                    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%^showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;p&amp;gt;{{ &#039;plugin.mod_certificate.requiredtimenotmet&#039; | translate }}&amp;lt;/p&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;!-- Call log WS when the template is loaded. --&amp;gt;&lt;br /&gt;
        &amp;lt;span core-site-plugins-call-ws-on-load name=&amp;quot;mod_certificate_view_certificate&amp;quot;&lt;br /&gt;
                [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/span&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache).&lt;br /&gt;
&lt;br /&gt;
Then we display the module description using &amp;lt;code&amp;gt;core-course-module-description&amp;lt;/code&amp;gt;, which is a component used to include the course module description.&lt;br /&gt;
&lt;br /&gt;
For displaying the certificate information we create a list of elements, adding a header on top.&lt;br /&gt;
&lt;br /&gt;
The following line using the &amp;lt;code&amp;gt;translate&amp;lt;/code&amp;gt; filter indicates that the app will translate the &amp;lt;code&amp;gt;summaryofattempts&amp;lt;/code&amp;gt; string id (here we could’ve used mustache translation but it is usually better to delegate the strings translations to the app). The string id has the following format: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
plugin.{plugin-identifier}.{string-id}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Where &amp;lt;code&amp;gt;{plugin-identifier}&amp;lt;/code&amp;gt; is taken from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;{string-id}&amp;lt;/code&amp;gt; must be indicated in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; field in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Then, we display a button to transition to another page if there are certificates issued. The attribute (directive) &amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt; indicates that if the user clicks the button, we need to call the &amp;lt;code&amp;gt;mobile_issues_view&amp;lt;/code&amp;gt; function in the &amp;lt;code&amp;gt;mod_certificate&amp;lt;/code&amp;gt; component; passing as arguments the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;. The content returned by this function will be displayed in a new page (read the following section to see the code of this new page).&lt;br /&gt;
&lt;br /&gt;
Just after this button, we display another one but this time for downloading an issued certificate. The &amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt; directive indicates that clicking this button is for downloading the whole activity and opening the main file. This means that, when the user clicks this button, the whole certificate activity will be available offline.&lt;br /&gt;
&lt;br /&gt;
Finally, just before the &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt; is closed, we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt; directive to indicate that once the page is loaded, we need to call a Web Service function in the server, in this case we are calling the &amp;lt;code&amp;gt;mod_certificate_view_certificate&amp;lt;/code&amp;gt; that will log that the user viewed this page.&lt;br /&gt;
&lt;br /&gt;
As you can see, no JavaScript was necessary at all. We used plain HTML elements and attributes that did all the complex dynamic logic (like calling a Web Service) behind the scenes.&lt;br /&gt;
&lt;br /&gt;
===Step 4. Adding an additional page===&lt;br /&gt;
Add the following method to &amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Returns the certificate issues view for the mobile app.&lt;br /&gt;
 * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
 *&lt;br /&gt;
 * @return array       HTML, JS and other data.&lt;br /&gt;
 */&lt;br /&gt;
public static function mobile_issues_view($args) {&lt;br /&gt;
    global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
    $args = (object) $args;&lt;br /&gt;
    $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
    // Capabilities check.&lt;br /&gt;
    require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
    $context = context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
    require_capability (&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
    if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
        require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
    }&lt;br /&gt;
    $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
    // Get certificates from external (taking care of exceptions).&lt;br /&gt;
    try {&lt;br /&gt;
        $issued = mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
        $certificates = mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
        $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
    } catch (Exception $e) {&lt;br /&gt;
        $issues = [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $data = [&#039;issues&#039; =&amp;gt; $issues];&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
            [&lt;br /&gt;
                &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_issues&#039;, $data),&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
        &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This method for the new page was added just after &amp;lt;code&amp;gt;mobile_course_view&amp;lt;/code&amp;gt;, the code is quite similar: checks the capabilities, retrieves the information required for the template, and returns the template rendered.&lt;br /&gt;
&lt;br /&gt;
The code of the mustache template is also very simple.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_issues.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &amp;lt;%timecreated%&amp;gt; | coreToLocaleString }}&amp;lt;/p&amp;gt;&lt;br /&gt;
                    &amp;lt;p&amp;gt;&amp;lt;%grade%&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As we did in the previous template, in the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache). &lt;br /&gt;
&lt;br /&gt;
Here we are creating an Ionic list that will display a new item in the list per each issued certificated.&lt;br /&gt;
&lt;br /&gt;
For the issued certificated we’ll display the time when it was created (using the app filter &amp;lt;code&amp;gt;coreToLocaleString&amp;lt;/code&amp;gt;). We are also displaying the grade displayed in the certificate (if any).&lt;br /&gt;
&lt;br /&gt;
===Step 5. Plugin webservices, if included===&lt;br /&gt;
If your plugin uses its own web services, they will also need to be enabled for mobile access in your &amp;lt;code&amp;gt;db/services.php&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
The following line should be included in each webservice definition:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;services&#039; =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/db/services.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$functions = [&lt;br /&gt;
    &#039;mod_certificate_get_certificates_by_courses&#039; =&amp;gt; [&lt;br /&gt;
        &#039;classname&#039;     =&amp;gt; &#039;mod_certificate_external&#039;,&lt;br /&gt;
        &#039;methodname&#039;    =&amp;gt; &#039;get_certificates_by_courses&#039;,&lt;br /&gt;
        &#039;description&#039;   =&amp;gt; &#039;Returns a list of certificate instances...&#039;,&lt;br /&gt;
        &#039;type&#039;          =&amp;gt; &#039;read&#039;,&lt;br /&gt;
        &#039;capabilities&#039;  =&amp;gt; &#039;mod/certificate:view&#039;,&lt;br /&gt;
        &#039;services&#039;      =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Mobile.php supported options==&lt;br /&gt;
In the previous section, we learned about some of the existing options for handlers configuration. This is the full list of supported options.&lt;br /&gt;
===Common options===&lt;br /&gt;
* &amp;lt;code&amp;gt;delegate&amp;lt;/code&amp;gt; (mandatory) — Name of the delegate to register the handler in.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (mandatory) — The method to call to retrieve the main page content.&lt;br /&gt;
* &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; (optional) — A method to call to retrieve the initialisation JS and the restrictions to apply to the whole handler. It can also return templates that can be used from JavaScript. You can learn more about this in the [[#Initialisation|Initialisation]] section.&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttocurrentuser&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForUser&amp;lt;/code&amp;gt; function. If true, the handler will only be shown for the current user. For more info about displaying the plugin only for certain users, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttoenrolledcourses&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForCourse&amp;lt;/code&amp;gt; function. If true or not defined, the handler will only be shown for courses the user is enrolled in. For more info about displaying the plugin only for certain courses, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;styles&amp;lt;/code&amp;gt; (optional) — An array with two properties: &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt;. The URL should point to a CSS file, either using an absolute URL or a relative URL. This file will be downloaded and applied by the app. It&#039;s recommended to include styles that will only affect your plugin templates. The version number is used to determine if the file needs to be downloaded again, you should change the version number everytime you change the CSS file.&lt;br /&gt;
* &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; (optional) — If your plugin supports a component in the app different than the one defined by your plugin, you can use this property to specify it. For example, you can create a local plugin to support a certain course format, activity, etc. The component of your plugin in Moodle would be &amp;lt;code&amp;gt;local_whatever&amp;lt;/code&amp;gt;, but in &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; you can specify that this handler will implement &amp;lt;code&amp;gt;format_whatever&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;mod_whatever&amp;lt;/code&amp;gt;. This property was introduced in the version 3.6.1 of the app.&lt;br /&gt;
===Options only for CoreMainMenuDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. Main Menu plugins are always displayed in the More tab, they cannot be displayed as tabs in the bottom bar.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreMainMenuHomeDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseOptionsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ismenuhandler&amp;lt;/code&amp;gt; (optional) — Supported from the 3.7.1 version of the app. Set it to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if you want your plugin to be displayed in the contextual menu of the course instead of in the top tabs. The contextual menu is displayed when you click in the 3-dots button at the top right of the course.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseModuleDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — Path to the module icon. After Moodle app 4.0, this icon is only used as a fallback, the app will always try to use the theme icon so themes can override icons in the app.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (optional) — The function to call to retrieve the main page content. In this delegate the method is optional. If the method is not set, the module won&#039;t be clickable.&lt;br /&gt;
* &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt; (optional) — List of functions to call when prefetching the module. It can be a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; method or a WS. You can filter the params received by the WS. By default, WS will receive these params: &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;. Other valid values that will be added if they are present in the list of params: &amp;lt;code&amp;gt;courseids&amp;lt;/code&amp;gt; (it will receive a list with the courses the user is enrolled in), &amp;lt;code&amp;gt;{component}id&amp;lt;/code&amp;gt; (For example, &amp;lt;code&amp;gt;certificateid&amp;lt;/code&amp;gt;).&lt;br /&gt;
* &amp;lt;code&amp;gt;downloadbutton&amp;lt;/code&amp;gt; (optional) — Whether to display download button in the module. If not defined, the button will be shown if there is any offlinefunction.&lt;br /&gt;
* &amp;lt;code&amp;gt;isresource&amp;lt;/code&amp;gt; (optional) — Whether the module is a resource or an activity. Only used if there is any offline function. If your module relies on the &amp;lt;code&amp;gt;contents&amp;lt;/code&amp;gt; field, then it should be &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;updatesnames&amp;lt;/code&amp;gt; (optional) — Only used if there is any offline function. A regular expression to check if there&#039;s any update in the module. It will be compared to the result of &amp;lt;code&amp;gt;core_course_check_updates&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayopeninbrowser&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Open in browser&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayOpenInBrowser = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydescription&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Description&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayDescription = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayrefresh&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Refresh&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayRefresh = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayprefetch&amp;lt;/code&amp;gt; (optional) — Whether the module should display the download option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayPrefetch = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysize&amp;lt;/code&amp;gt; (optional) — Whether the module should display the downloaded size in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displaySize = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; (optional) — It can be used to specify the supported features of the plugin. Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. It should be an array with features as keys (For example, &amp;lt;code&amp;gt;[FEATURE_NO_VIEW_LINK =&amp;gt; true&amp;lt;/code&amp;gt;). If you need to calculate this dynamically please see [[#Module_plugins:_dynamically_determine_if_a_feature_is_supported|Module plugins: dynamically determine if a feature is supported]]. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; (optional) — If set, this method will be called when the course is rendered and the HTML returned will be displayed in the course page for the module. Please notice the HTML returned should not contain directives or components, only default HTML. Supported from the 3.8 version of the app.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreCourseFormatDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;canviewallsections&amp;lt;/code&amp;gt; (optional) — Whether the course format allows seeing all sections in a single page. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayenabledownload&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, it&#039;s no longer used.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysectionselector&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, use &#039;&#039;displaycourseindex&#039;&#039; instead.&lt;br /&gt;
*&amp;lt;code&amp;gt;displaycourseindex&amp;lt;/code&amp;gt; (optional) — Whether the default course index should be displayed. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
===Options only for CoreUserDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — The type of the addon. The values accepted are &amp;lt;code&amp;gt;&#039;newpage&#039;&amp;lt;/code&amp;gt; (default) and &amp;lt;code&amp;gt;&#039;communication&#039;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreSettingsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for AddonMessageOutputDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreBlockDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (optional):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section. If this is not supplied, it will default to &amp;lt;code&amp;gt;&#039;plugins.block_{block-name}.pluginname&#039;&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class. If this is not supplied, it will default to &amp;lt;code&amp;gt;block_{block-name}&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — Possible values are:&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;title&amp;quot;&amp;lt;/code&amp;gt; — Your block will only display the block title, and when it&#039;s clicked it will open a new page to display the block contents (the template returned by the block&#039;s method).&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;prerendered&amp;quot;&amp;lt;/code&amp;gt; — Your block will display the content and footer returned by the WebService to get the blocks (for example, &amp;lt;code&amp;gt;core_block_get_course_blocks&amp;lt;/code&amp;gt;), so your block&#039;s method will never be called.&lt;br /&gt;
*** Any other value — Your block will immediately call the method specified in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and it will use the template to render the block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fallback&amp;lt;/code&amp;gt; (optional) — This option allows you to specify a block to use in the app instead of your block. For example, you can make the app display the &amp;quot;My overview&amp;quot; block instead of your block in the app by setting &amp;lt;code&amp;gt;&#039;fallback&#039; =&amp;gt; &#039;myoverview&#039;&amp;lt;/code&amp;gt;. The fallback will only be used if you don&#039;t specify a &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; is different to &amp;lt;code&amp;gt;&#039;title&#039;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&#039;prerendered&#039;&amp;lt;/code&amp;gt;. Supported from the 3.9.0 version of the app.&lt;br /&gt;
==Delegates==&lt;br /&gt;
Delegates can be classified by type of plugin. For more info about type of plugins, please see the [[#Types_of_plugins|Types of plugins]] section.&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new items to the main menu (currently displayed at the bottom of the app). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuHomeDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new tabs in the home page (by default the app is displaying the &amp;quot;Dashboard&amp;quot; and &amp;quot;Site home&amp;quot; tabs). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new options in a course (Participants or Grades are examples of this type of delegate).&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting activity modules or resources.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add additional options in the user profile page in the app.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting course formats. When you open a course from the course list in the mobile app, it will check if there is a &amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt; handler for the format that site uses. If so, it will display the course using that handler. Otherwise, it will use the default app course format.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile course formats]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreSettingsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to add a new option in the settings page.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonMessageOutputDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a message output plugin.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreBlockDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a block. For example, blocks can be displayed in Site Home, Dashboard and the Course page.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question types.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile question types]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionBehaviourDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question behaviours.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting user profile fields.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModQuizAccessRuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a quiz access rule.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModAssignSubmissionDelegate&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;AddonModAssignFeedbackDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use these delegates to support assign submission or feedback plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonWorkshopAssessmentStrategyDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a workshop assessment strategy plugin.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
These delegates require JavaScript to be supported. See [[#Initialisation|Initialisation]] for more information.&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreContentLinksDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreCourseModulePrefetchDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFileUploaderDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CorePluginFileDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFilterDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
==Available components and directives==&lt;br /&gt;
===Difference between components and directives===&lt;br /&gt;
A directive is usually represented as an HTML attribute, allows you to extend a piece of HTML with additional information or functionality. Example of directives are: &amp;lt;code&amp;gt;core-auto-focus&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*ngIf&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ng-repeat&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components are also directives, but they are usually represented as an HTML tag and they are used to add custom elements to the app. Example of components are &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-item&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;core-search-box&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components and directives are Angular concepts; you can learn more about them and the components come out of the box with Ionic in the following links:&lt;br /&gt;
* [https://angular.io/guide/built-in-directives Angular directives documentation]&lt;br /&gt;
* [https://ionicframework.com/docs/components Ionic components]&lt;br /&gt;
===Custom core components and directives===&lt;br /&gt;
These are some useful custom components and directives that are only available in the Moodle App. Please note that this isn’t the full list of custom components and directives, it’s just an extract of the most common ones.&lt;br /&gt;
&lt;br /&gt;
You can find a full list of components and directives in the source code of the app, within [https://github.com/moodlehq/moodleapp/tree/master/src/core/components &amp;lt;code&amp;gt;src/core/components&amp;lt;/code&amp;gt;] and [https://github.com/moodlehq/moodleapp/tree/master/src/core/directives &amp;lt;code&amp;gt;src/core/directives&amp;lt;/code&amp;gt;].&lt;br /&gt;
====&amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;====&lt;br /&gt;
This directive formats the text and adds some directives needed for the app to work as it should. For example, it treats all links and all the embedded media so they work fine in the app. If some content in your template includes links or embedded media, please use this directive.&lt;br /&gt;
&lt;br /&gt;
This directive automatically applies &amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt; to all the links and embedded media.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;text&amp;lt;/code&amp;gt; (string) — The text to format.&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;adaptImg&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to adapt images to screen width. &lt;br /&gt;
* &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether all HTML tags should be removed.&lt;br /&gt;
* &amp;lt;code&amp;gt;singleLine&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether new lines should be removed to display all the text in single line. Only if &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; (number) — Optional. Max height in pixels to render the content box. The minimum accepted value is 50. Using this parameter will force &amp;lt;code&amp;gt;display: block&amp;lt;/code&amp;gt; to calculate the height better. If you want to avoid this, use &amp;lt;code&amp;gt;class=&amp;quot;inline&amp;quot;&amp;lt;/code&amp;gt; at the same time to use &amp;lt;code&amp;gt;display: inline-block&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;fullOnClick&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether it should open a new page with the full contents on click. Only if &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; is set and the content has been collapsed. &lt;br /&gt;
* &amp;lt;code&amp;gt;fullTitle&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;Description&amp;quot;&amp;lt;/code&amp;gt;. Title to use in full view.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-format-text text=&amp;quot;&amp;lt;% cm.description %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle a link. It performs several checks, like checking if the link needs to be opened in the app, and opens the link as it should (without overriding the app).&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;capture&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether the link needs to be captured by the app (check if the link can be handled by the app instead of opening it in a browser).&lt;br /&gt;
* &amp;lt;code&amp;gt;inApp&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to open in an embedded browser within the app or in the system browser.&lt;br /&gt;
* &amp;lt;code&amp;gt;autoLogin&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt;. If the link should be open with auto-login. Accepts the following values:&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;yes&amp;quot;&amp;lt;/code&amp;gt; — Always auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;no&amp;quot;&amp;lt;/code&amp;gt; — Never auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt; — Auto-login only if it points to the current site.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a href=&amp;quot;&amp;lt;% cm.url %&amp;gt;&amp;quot; core-link&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle links to files and embedded files. This directive should be used in any link to a file or any embedded file that you want to have available when the app is offline. &lt;br /&gt;
&lt;br /&gt;
If a file is downloaded, its URL will be replaced by the local file URL.&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;&amp;lt;% event.iconurl %&amp;gt;&amp;quot; core-external-content component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% event.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-user-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to go to user profile on click. When the user clicks the element where this directive is attached, the right user profile will be opened.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;userId&amp;lt;/code&amp;gt; (number) — User id to open the profile.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — Optional. Course id to show the user info related to that course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a ion-item core-user-link userId=&amp;quot;&amp;lt;% userid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to handle a remote file. It shows the file name, icon (depending on mime type) and a button to download or refresh it. The user can identify if the file is downloaded or not based on the button.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;file&amp;lt;/code&amp;gt; (object) — The file. Must have a &amp;lt;code&amp;gt;filename&amp;lt;/code&amp;gt; property and either &amp;lt;code&amp;gt;fileurl&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component the file belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDelete&amp;lt;/code&amp;gt; (boolean) — Optional. Whether the file can be deleted.&lt;br /&gt;
* &amp;lt;code&amp;gt;alwaysDownload&amp;lt;/code&amp;gt; (boolean) — Optional. Whether it should always display the refresh button when the file is downloaded. Use it for files that you cannot determine if they&#039;re outdated or not.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDownload&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether file can be downloaded. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-file&lt;br /&gt;
        [file]=&amp;quot;{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.url %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.name %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            filesize: &#039;&amp;lt;% issue.size %&amp;gt;&#039;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/core-file&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening a file. When the item with this directive is clicked, the file will be downloaded (if needed) and opened.&lt;br /&gt;
&lt;br /&gt;
It is usually recommended to use the &amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt; component since it also displays the state of the file.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt; (object) — The file to download.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. Component ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage (a button to download a file):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button&lt;br /&gt;
        [core-download-file]=&amp;quot;{&lt;br /&gt;
            fileurl: &amp;lt;% issue.url %&amp;gt;,&lt;br /&gt;
            timemodified: &amp;lt;% issue.timemodified %&amp;gt;,&lt;br /&gt;
            filesize: &amp;lt;% issue.size %&amp;gt;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.download | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening the main file of a module.&lt;br /&gt;
&lt;br /&gt;
When the item with this directive is clicked, the whole module will be downloaded (if needed) and its main file opened. This is meant for modules like &amp;lt;code&amp;gt;mod_resource&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This directive must receive either a &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; or a &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. If no files are provided, it will use &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; (object) — Optional, required if module is not supplied. The module object.&lt;br /&gt;
* &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt; (number) — Optional, required if module is not supplied. The module ID.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — The course ID the module belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional, defaults to the same value as &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. Component ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; (object[]) — Optional. List of files of the module. If not provided, uses &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot; &lt;br /&gt;
        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        [files]=&amp;quot;[{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
        }]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-navbar-buttons&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to add buttons to the app&#039;s header without having to place them inside the header itself. Using this component in a site plugin will allow adding buttons to the header of the current page.&lt;br /&gt;
&lt;br /&gt;
If this component indicates a position (start/end), the buttons will only be added if the header has some buttons in that position. If no start/end is specified, then the buttons will be added to the first &amp;lt;code&amp;gt;&amp;lt;ion-buttons&amp;gt;&amp;lt;/code&amp;gt; found in the header.&lt;br /&gt;
&lt;br /&gt;
You can use the &amp;lt;code&amp;gt;[hidden]&amp;lt;/code&amp;gt; input to hide all the inner buttons if a certain condition is met.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons end&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button (click)=&amp;quot;action()&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-icon slot=&amp;quot;icon-only&amp;quot; name=&amp;quot;funnel&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can also use this to add options to the context menu, for example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons&amp;gt;&lt;br /&gt;
    &amp;lt;core-context-menu&amp;gt;&lt;br /&gt;
        &amp;lt;core-context-menu-item&lt;br /&gt;
                [priority]=&amp;quot;500&amp;quot; content=&amp;quot;Nice boat&amp;quot; (action)=&amp;quot;boatFunction()&amp;quot;&lt;br /&gt;
                iconAction=&amp;quot;boat&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/core-context-menu-item&amp;gt;&lt;br /&gt;
    &amp;lt;/core-context-menu&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Using &#039;font&#039; icons with &amp;lt;code&amp;gt;ion-icon&amp;lt;/code&amp;gt;====&lt;br /&gt;
&lt;br /&gt;
Font icons are widely used on the app and Moodle LMS website. In order to support [https://fontawesome.com/v5/search?m=free font awesome icons]. We&#039;ve added a directive that uses prefixes on the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; attribute to use different font icons.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fas-&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;fa-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=solid Font awesome solid] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;far-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=regular Font awesome regular] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fab-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=brands Font awesome brands] library (But only a few are supported and we discourage to use them).&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;moodle-&amp;lt;/code&amp;gt; will use some svg icons [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/moodle imported from Moodle LMS].&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fam-&amp;lt;/code&amp;gt; will use [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/font-awesome customized font awesome icons].&lt;br /&gt;
* If the prefix is not found or not valid, the app will search the icon name on the [https://ionic.io/ionicons ionicons library].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example of usage to show icon &amp;quot;pizza-slice&amp;quot; from font-awesome regular library:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-icon name=&amp;quot;fas-pizza-slice&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We encourage the use of font-awesome icons to match the appearance from the LMS website version.&lt;br /&gt;
&lt;br /&gt;
===Specific component and directives for plugins===&lt;br /&gt;
These are component and directives created specifically for supporting Moodle plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to display a new content when clicked. This new content can be displayed in a new page or in the current page (only if the current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The params to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in v3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in same page or open a new one. &lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that doing &amp;lt;code&amp;gt;[useOtherData]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to go to a new content page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content &lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; &lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to load new content in current page using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot; samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked. The action to do when the WS call is successful depends on the provided data: display a message, go back or refresh current view.&lt;br /&gt;
&lt;br /&gt;
If you want to load a new content when the WS call is done, please see [[#core-site-plugins-call-ws-new-content|&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The params for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;successMessage&amp;lt;/code&amp;gt; (string) — Message to show on success. If not supplied, no message. If supplied but empty, defaults to &amp;quot;Success&amp;quot;.&lt;br /&gt;
* &amp;lt;code&amp;gt;goBackOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to go back if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to refresh the current view if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to send some data to the server without using cache, displaying default messages and refreshing on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage successMessage&lt;br /&gt;
        refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to send some data to the server using cache without confirming, going back on success and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        goBackOnSuccess=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Same as the previous example, but implementing custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;certificateViewed($event)&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.certificateViewed = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked and load a new content passing the WS result as arguments. This new content can be displayed in a new page or in the same page (only if current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to load some new content when done, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The parameters to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in the same page or open a new one.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. The format is the same as in &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;jsData&amp;lt;/code&amp;gt; (any) — JS variables to pass to the new page so they can be used in the template or JS. If &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; is supplied instead of an object, all initial variables from current page will be copied. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;newContentPreSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in 3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to get some data from the server without using cache, showing default confirm and displaying a new page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage&lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to get some data from the server using cache, without confirm, displaying new content in same page and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Same as the previous example, but implementing a custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS as soon as the template is loaded. This directive is meant for actions to do in the background, like calling logging Web Services.&lt;br /&gt;
&lt;br /&gt;
If you want to call a WS when the user clicks on a certain element, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span core-site-plugins-call-ws-on-load&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Advanced features==&lt;br /&gt;
===Display the plugin only if certain conditions are met===&lt;br /&gt;
You might want to display your plugin in the mobile app only if certain dynamic conditions are met, so the plugin would be displayed only for some users. This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section ahead).&lt;br /&gt;
&lt;br /&gt;
All initialisation methods are called as soon as your plugin is retrieved. If you don&#039;t want your plugin to be displayed for the current user, then you should return the following in the initialisation method (only for Moodle site 3.8 and onwards):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;disabled&#039; =&amp;gt; true];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If the Moodle site is older than 3.8, then the initialisation method should return this instead:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;javascript&#039; =&amp;gt; &#039;this.HANDLER_DISABLED&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
On the other hand, you might want to display a plugin only for certain courses (&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;) or only if the user is viewing certain users&#039; profiles (&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;). This can be achieved with the initialisation method too.&lt;br /&gt;
&lt;br /&gt;
In the initialisation method you can return a &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; property with two fields in it: &amp;lt;code&amp;gt;courses&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;users&amp;lt;/code&amp;gt;. If you return a list of courses IDs in this property, then your plugin will only be displayed when the user views any of those courses. In the same way, if you return a list of user IDs then your plugin will only be displayed when the user views any of those users&#039; profiles.&lt;br /&gt;
===Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;===&lt;br /&gt;
The values returned by the functions in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; are added to a variable so they can be used both in JavaScript and in templates. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call is added to a variable named &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt;, while the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call is added to a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call will be passed to the JS and template of all the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; calls in that handler. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call will only be passed to the JS and template returned by that &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call.&lt;br /&gt;
&lt;br /&gt;
This means that, in your JavaScript, you can access and use the data like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.CONTENT_OTHERDATA.myVar;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the template you could use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
{{ CONTENT_OTHERDATA.myVar }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; is the name we put to one of our variables, it can be any name that you want. In the example above, this is the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
[&#039;myVar&#039; =&amp;gt; &#039;Initial value&#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Example====&lt;br /&gt;
In our plugin, we want to display an input text with a certain initial value. When the user clicks a button, we want the value in the input to be sent to a certain Web Service. This can be done using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We will return the initial value of the input in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; of our PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;myVar&#039; =&amp;gt; &#039;My initial value&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then in the template we will use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item text-wrap&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label position=&amp;quot;stacked&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.textlabel | translate }}&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;text&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.myVar&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws name=&amp;quot;mod_certificate_my_webservice&amp;quot; [useOtherDataForWS]=&amp;quot;[&#039;myVar&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we are creating an input text and we use &amp;lt;code&amp;gt;[(ngModel)]&amp;lt;/code&amp;gt; to use the value in &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; as the initial value and to store the changes in the same &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This means that the initial value of the input will be &amp;quot;My initial value&amp;quot;, and if the user changes the value of the input these changes will be applied to the &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This is called 2-way data binding in Angular.&lt;br /&gt;
&lt;br /&gt;
Then we add a button to send this data to a WS, and for that we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive. We use the &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; attribute to specify which variable from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; we want to send to our WebService. So if the user enters &amp;quot;A new value&amp;quot; in the input and then clicks the button, it will call the WebService &amp;lt;code&amp;gt;mod_certificate_my_webservice&amp;lt;/code&amp;gt; and will send as a parameter &amp;lt;code&amp;gt;[&#039;myVar&#039; =&amp;gt; &#039;A new value&#039;]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We can also achieve the same result using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; attribute of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive instead of using &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws &lt;br /&gt;
        name=&amp;quot;mod_certificate_my_webservice&amp;quot; [params]=&amp;quot;{myVar: CONTENT_OTHERDATA.myVar}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The Web Service call will be exactly the same with both versions.&lt;br /&gt;
&lt;br /&gt;
Notice that this example could be done without using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; too, using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; input of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive.&lt;br /&gt;
===Running JS code after a content template has loaded===&lt;br /&gt;
When you return JavaScript code from a handler function using the &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; array key, this code is executed immediately after the web service call returns, which may be before the returned template has been rendered into the DOM. &lt;br /&gt;
&lt;br /&gt;
If your code needs to run after the DOM has been updated, you can use &amp;lt;code&amp;gt;setTimeout&amp;lt;/code&amp;gt; to call it. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&lt;br /&gt;
    &#039;template&#039; =&amp;gt; [&lt;br /&gt;
        // ...&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;javascript&#039; =&amp;gt; &#039;setTimeout(function() { console.log(&amp;quot;DOM is available now&amp;quot;); });&#039;,&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    &#039;files&#039; =&amp;gt; [],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice that if you wanted to write a lot of code here, you might be better off putting it in a function defined in the response from an initialisation template, so that it does not get loaded again with each page of content.&lt;br /&gt;
===JS functions visible in the templates===&lt;br /&gt;
The app provides some JavaScript functions that can be used from the templates to update, refresh or view content. These are the functions:&lt;br /&gt;
* &amp;lt;code&amp;gt;openContent(title: string, args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Open a new page to display some new content. You need to specify the &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; of the new page and the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshContent(showSpinner = true)&amp;lt;/code&amp;gt; — Refresh the current content. By default, it will display a spinner while refreshing. If you don&#039;t want it to be displayed, you should pass &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; as a parameter.&lt;br /&gt;
* &amp;lt;code&amp;gt;updateContent(args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Refresh the current content using different parameters. You need to specify the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Group selector=====&lt;br /&gt;
Imagine we have an activity that uses groups and we want to let the user select which group they want to see. A possible solution would be to return all the groups in the same template (hidden), and then show the group user selects. However, we can make it more dynamic and return only the group the user is requesting.&lt;br /&gt;
&lt;br /&gt;
To do so, we&#039;ll use a drop down to select the group. When the user selects a group using this drop down, we&#039;ll update the page content to display the new group.&lt;br /&gt;
&lt;br /&gt;
The main difficulty in this is to tell the view which group needs to be selected when the view is loaded. There are 2 ways to do it: using plain HTML or using Angular&#039;s &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;.&lt;br /&gt;
======Using plain HTML======&lt;br /&gt;
We need to add a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; attribute to the option that needs to be selected. To do so, we need to pre-caclulate the selected option in the PHP code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// Detect which group is selected.&lt;br /&gt;
foreach ($groups as $gid=&amp;gt;$group) {&lt;br /&gt;
    $group-&amp;gt;selected = $gid === $groupid;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$data = [&lt;br /&gt;
    &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
    &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
    &#039;groups&#039; =&amp;gt; $groups,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the code above, we&#039;re retrieving the groups the user can see and then we&#039;re adding a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; boolean to each one to determine which one needs to be selected in the drop down. Finally, we pass the list of groups to the template.&lt;br /&gt;
&lt;br /&gt;
In the template, we display the drop down like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: $event})&amp;quot; interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot; &amp;lt;%#selected%&amp;gt;selected&amp;lt;%/selected%&amp;gt; &amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The &amp;lt;code&amp;gt;ionChange&amp;lt;/code&amp;gt; function will be called every time the user selects a different group with the drop down. We&#039;re using the &amp;lt;code&amp;gt;updateContent&amp;lt;/code&amp;gt; function to update the current view using the new group. &amp;lt;code&amp;gt;$event&amp;lt;/code&amp;gt; is an Angular variable that will have the selected value (in our case, the group ID that was just selected). This is enough to make the group selector work.&lt;br /&gt;
======Using &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;======&lt;br /&gt;
&amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; is an Angular directive that allows storing the value of a certain input or select in a JavaScript variable, and also the opposite way: tell the input or select which value to set. The main problem is that we cannot initialise a JavaScript variable from the template, so we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the PHP function we&#039;ll return the group that needs to be selected in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
        &#039;group&#039; =&amp;gt; $groupid,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we don&#039;t need to iterate over the groups array like in the plain HTML example. However, now we&#039;re returning the group id in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array. As it&#039;s explained in the [[#Using_otherdata|Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;]] section, this &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; is visible in the templates inside a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. So in the template we&#039;ll use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.group&amp;quot;&lt;br /&gt;
        (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: CONTENT_OTHERDATA.group})&amp;quot;&lt;br /&gt;
        interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Use the rich text editor===&lt;br /&gt;
The rich text editor included in the app requires a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt; to work. You can use the &amp;lt;code&amp;gt;FormBuilder&amp;lt;/code&amp;gt; library to create this control (or to create a whole &amp;lt;cide&amp;gt;FormGroup if you prefer).&lt;br /&gt;
&lt;br /&gt;
With the following JavaScript you&#039;ll be able to create a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.control = this.FormBuilder.control(this.CONTENT_OTHERDATA.rte);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we&#039;re using a value returned in &amp;lt;code&amp;gt;OTHERDATA&amp;lt;/code&amp;gt; as the initial value of the rich text editor, but you can use whatever you want.&lt;br /&gt;
&lt;br /&gt;
Then you need to pass this control to the rich text editor in your template:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;core-rich-text-editor item-content [control]=&amp;quot;control&amp;quot; placeholder=&amp;quot;Enter your text here&amp;quot; name=&amp;quot;rte_answer&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-rich-text-editor&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, there are several ways to send the value in the rich text editor to a Web Service to save it. This is one of the simplest options:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_webservice&amp;quot; [params]=&amp;quot;{rte: control.value}&amp;quot; ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re passing the value of the rich text editor as a parameter to our Web Service.&lt;br /&gt;
===Initialisation===&lt;br /&gt;
All handlers can specify an &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; method in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. This method is meant to return some JavaScript code that needs to be executed as soon as the plugin is retrieved.&lt;br /&gt;
&lt;br /&gt;
When the app retrieves all the handlers, the first thing it will do is call the &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt; Web Service with the initialisation method. This WS call will only receive the default arguments.&lt;br /&gt;
&lt;br /&gt;
The app will immediately execute the JavaScript code returned by this WS call. This JavaScript can be used to manually register your handlers in the delegates you want, without having to rely on the default handlers built based on the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; data.&lt;br /&gt;
&lt;br /&gt;
The templates returned by this method will be added to a &amp;lt;code&amp;gt;INIT_TEMPLATES&amp;lt;/code&amp;gt; variable that will be passed to all the JavaScript code of that handler. This means that the JavaScript returned by the initialisation method or the main method can access any of the templates HTML like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.INIT_TEMPLATES[&#039;main&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In this case, &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is the ID of the template we want to use.&lt;br /&gt;
&lt;br /&gt;
The same happens with the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the initialisation method, it is added to an &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt; variable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; field returned by this call will be used to determine if your handler is enabled or not. For example, if your handler is for the delegate &amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt; and you return a list of course ids in &amp;lt;code&amp;gt;restrict.courses&amp;lt;/code&amp;gt;, then your handler will only be enabled in the courses you returned. This only applies to the default handlers, if you register your own handler using the JavaScript code then you should check yourself if the handler is enabled.&lt;br /&gt;
&lt;br /&gt;
Finally, if you return an object in this initialisation JavaScript code, all the properties of that object will be passed to all the JavaScript code of that handler so you can use them when the code is run. For example, if your JavaScript code does something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    MyAddonClass: new MyAddonClass()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then, for the rest of JavaScript code of your handler (for example, the main method) you can use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.MyAddonClass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Link handlers=====&lt;br /&gt;
A link handler allows you to decide what to do when a link with a certain URL is clicked. This is useful, for example, to open your plugin page when a link to your plugin is clicked.&lt;br /&gt;
&lt;br /&gt;
After the 4.0 version, the Moodle app automatically creates two link handlers for module plugins, you don&#039;t need to create them in your plugin&#039;s Javascript code anymore:&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginanme/view.php?id=X&#039;&#039;. When this link is clicked, it will open your module in the app.&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginname/index.php?id=X&#039;&#039;. When this link is clicked, it will open a page in the app listing all the modules of your type inside a certain course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Link handlers have some advanced features that allow you to change how links behave under different conditions.&lt;br /&gt;
======Patterns======&lt;br /&gt;
You can define a Regular Expression pattern to match certain links. This will apply the handler only to links that match the pattern.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = RegExp(&#039;\/mod\/foo\/specialpage.php&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Priority======&lt;br /&gt;
Multiple link handlers may apply to a given link. You can define the order of precedence by setting the priority; the handler with the highest priority will be used.&lt;br /&gt;
&lt;br /&gt;
All default handlers have a priority of 0, so 1 or higher will override the default.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Multiple actions======&lt;br /&gt;
Once a link has been matched, the handler&#039;s &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method determines what the link should do. This method has access to the URL and its parameters.&lt;br /&gt;
&lt;br /&gt;
Different actions can be returned depending on different conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                action: function(siteId, navCtrl) {&lt;br /&gt;
                    // The actual behaviour of the link goes here.&lt;br /&gt;
                },&lt;br /&gt;
                sites: [&lt;br /&gt;
                    // ...&lt;br /&gt;
                ],&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                // ...&lt;br /&gt;
            },&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once handlers have been matched for a link, the actions will be fetched for all the matching handlers, in priorty order. The first valid action will be used to open the link.&lt;br /&gt;
&lt;br /&gt;
If your handler is matched with a link, but a condition assessed in the &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method means you want to revert to the next highest priorty handler, you can invalidate your action by settings its sites propety to an empty array.&lt;br /&gt;
======Complex example======&lt;br /&gt;
This will match all URLs containing &amp;lt;code&amp;gt;/mod/foo/&amp;lt;/code&amp;gt;, and force those with an id parameter that&#039;s not in the &amp;lt;code&amp;gt;supportedModFoos&amp;lt;/code&amp;gt; array to open in the user&#039;s browser, rather than the app.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const that = this;&lt;br /&gt;
const supportedModFoos = [...];&lt;br /&gt;
&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = new RegExp(&#039;\/mod\/foo\/&#039;);&lt;br /&gt;
        this.name = &#039;AddonModFooLinkHandler&#039;;&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {     &lt;br /&gt;
        const action = {&lt;br /&gt;
            action() {&lt;br /&gt;
                that.CoreUtilsProvider.openInBrowser(url);&lt;br /&gt;
            },&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (supportedModFoos.indexOf(parseInt(params.id)) !== -1) {&lt;br /&gt;
            action.sites = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return [action];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreContentLinksDelegate.registerHandler(new AddonModFooLinkHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Module prefetch handler=====&lt;br /&gt;
The &amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt; handler allows you to define a list of offline functions to prefetch a module. However, you might want to create your own prefetch handler to determine what needs to be downloaded. For example, you might need to chain WS calls (pass the result of a WS call to the next one), and this cannot be done using offline functions.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to create a prefetch handler using the initialisation JS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Create a class that extends from CoreCourseActivityPrefetchHandlerBase.&lt;br /&gt;
class AddonModCertificateModulePrefetchHandler extends CoreCourseActivityPrefetchHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.name = &#039;AddonModCertificateModulePrefetchHandler&#039;;&lt;br /&gt;
        this.modName = &#039;certificate&#039;;&lt;br /&gt;
&lt;br /&gt;
        // This must match the plugin identifier from db/mobile.php,&lt;br /&gt;
        // otherwise the download link in the context menu will not update correctly.&lt;br /&gt;
        this.component = &#039;mod_certificate&#039;;&lt;br /&gt;
        this.updatesNames = /^configuration$|^.*files$/;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Override the prefetch call.&lt;br /&gt;
    prefetch(module, courseId, single, dirPath) {&lt;br /&gt;
        return this.prefetchPackage(module, courseId, single, prefetchCertificate);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prefetchCertificate(module, courseId, single, siteId) {&lt;br /&gt;
    // Perform all the WS calls.&lt;br /&gt;
    // You can access most of the app providers using that.ClassName. E.g. that.CoreWSProvider.call().&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseModulePrefetchDelegate.registerHandler(new AddonModCertificateModulePrefetchHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
One relatively simple full example is where you have a function that needs to work offline, but it has an additional argument other than the standard ones. You can imagine for this an activity like the book module, where it has multiple pages for the same &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;. The app will not automatically work with this situation — it will call the offline function with the standard arguments only — so you won&#039;t be able to prefetch all the possible parameters.&lt;br /&gt;
&lt;br /&gt;
To deal with this, you need to implement a web service in your Moodle component that returns the list of possible extra arguments, and then you can call this web service and loop around doing the same thing the app does when it prefetches the offline functions. Here is an example from a third-party module (showing only the actual prefetch function, the rest of the code is as above) where there are multiple values of a custom &amp;lt;code&amp;gt;section&amp;lt;/code&amp;gt; parameter for the mobile function &amp;lt;code&amp;gt;mobile_document_view&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function prefetchOucontent(module, courseId, single, siteId) {&lt;br /&gt;
    var component = &#039;mod_oucontent&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Get the site, first.&lt;br /&gt;
    return that.CoreSitesProvider.getSite(siteId).then(function(site) {&lt;br /&gt;
        // Read the list of pages in this document using a web service.&lt;br /&gt;
        return site.read(&#039;mod_oucontent_get_page_list&#039;, {&#039;cmid&#039;: module.id}).then(function(response) {&lt;br /&gt;
            var promises = [];&lt;br /&gt;
&lt;br /&gt;
            // For each page, read and process the page - this is a copy of logic in the app at&lt;br /&gt;
            // siteplugins.ts (prefetchFunctions), but modified to add the custom argument.&lt;br /&gt;
            for(var i = 0; i &amp;lt; response.length; i++) {&lt;br /&gt;
                var args = {&lt;br /&gt;
                    courseid: courseId,&lt;br /&gt;
                    cmid: module.id,&lt;br /&gt;
                    userid: site.getUserId()&lt;br /&gt;
                };&lt;br /&gt;
                if (response[i] !== &#039;&#039;) {&lt;br /&gt;
                    args.section = response[i];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                promises.push(that.CoreSitePluginsProvider.getContent(&lt;br /&gt;
                        component, &#039;mobile_document_view&#039;, args).then(&lt;br /&gt;
                        function(result) {&lt;br /&gt;
                            var subPromises = [];&lt;br /&gt;
                            if (result.files &amp;amp;&amp;amp; result.files.length) {&lt;br /&gt;
                                subPromises.push(that.CoreFilepoolProvider.downloadOrPrefetchFiles(&lt;br /&gt;
                                        site.id, result.files, true, false, component, module.id));&lt;br /&gt;
                            }&lt;br /&gt;
                            return Promise.all(subPromises);&lt;br /&gt;
                        }));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(promises);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Single activity course format=====&lt;br /&gt;
In the following example, the value of &amp;lt;code&amp;gt;INIT_TEMPLATES[&#039;main&#039;]&amp;lt;/code&amp;gt; is:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;componentClass&amp;quot; [data]=&amp;quot;data&amp;quot;&amp;gt;&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This template is returned by the initialisation method. And this is the JavaScript code returned:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatComponent {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.data = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ngOnChanges(changes) {&lt;br /&gt;
        var self = this;&lt;br /&gt;
&lt;br /&gt;
        if (this.course &amp;amp;&amp;amp; this.sections &amp;amp;&amp;amp; this.sections.length) {&lt;br /&gt;
            var module = this.sections[0] &amp;amp;&amp;amp; this.sections[0].modules &amp;amp;&amp;amp; this.sections[0].modules[0];&lt;br /&gt;
            if (module &amp;amp;&amp;amp; !this.componentClass) {&lt;br /&gt;
                that.CoreCourseModuleDelegate.getMainComponent(that.Injector, this.course, module).then((component) =&amp;gt; {&lt;br /&gt;
                    self.componentClass = component || that.CoreCourseUnsupportedModuleComponent;&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.data.courseId = this.course.id;&lt;br /&gt;
            this.data.module = module;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    doRefresh(refresher, done) {&lt;br /&gt;
        return Promise.resolve(this.dynamicComponent.callComponentFunction(&amp;quot;doRefresh&amp;quot;, [refresher, done]));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatHandler {&lt;br /&gt;
    &lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.name = &#039;singleactivity&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    isEnabled() {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canViewAllSections() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseTitle(course, sections) {&lt;br /&gt;
        if (sections &amp;amp;&amp;amp; sections[0] &amp;amp;&amp;amp; sections[0].modules &amp;amp;&amp;amp; sections[0].modules[0]) {&lt;br /&gt;
            return sections[0].modules[0].name;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return course.fullname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displayEnableDownload() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displaySectionSelector() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseFormatComponent() {&lt;br /&gt;
        return that.CoreCompileProvider.instantiateDynamicComponent(that.INIT_TEMPLATES[&#039;main&#039;], AddonSingleActivityFormatComponent);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseFormatDelegate.registerHandler(new AddonSingleActivityFormatHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using the JavaScript API===&lt;br /&gt;
The JavaScript API is only supported by the delegates specified in the [[#Templates_downloaded_on_login_and_rendered_using_JS_data_2|Templates downloaded on login and rendered using JS data]] section. This API allows you to override any of the functions of the default handler.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; specified in a handler registered in the &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; will be called immediately after the initialisation method, and the JavaScript returned by this method will be run. If this JavaScript code returns an object with certain functions, these functions will override the ones in the default handler.&lt;br /&gt;
&lt;br /&gt;
For example, if the JavaScript returned by the method returns something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The the &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function of the default handler will be overridden by the returned &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
The default handler for &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; only has 2 functions: &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt;. In addition, the JavaScript code can return an extra function named &amp;lt;code&amp;gt;componentInit&amp;lt;/code&amp;gt; that will be executed when the component returned by &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; is initialised.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to support the text user profile field using this API:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
var result = {&lt;br /&gt;
    componentInit: function() {&lt;br /&gt;
        if (this.field &amp;amp;&amp;amp; this.edit &amp;amp;&amp;amp; this.form) {&lt;br /&gt;
            this.field.modelName = &#039;profile_field_&#039; + this.field.shortname;&lt;br /&gt;
&lt;br /&gt;
            if (this.field.param2) {&lt;br /&gt;
                this.field.maxlength = parseInt(this.field.param2, 10) || &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.field.inputType = that.CoreUtilsProvider.isTrueOrOne(this.field.param3) ? &#039;password&#039; : &#039;text&#039;;&lt;br /&gt;
&lt;br /&gt;
            var formData = {&lt;br /&gt;
                value: this.field.defaultdata,&lt;br /&gt;
                disabled: this.disabled,&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            this.form.addControl(this.field.modelName,&lt;br /&gt;
                that.FormBuilder.control(formData, this.field.required &amp;amp;&amp;amp; !this.field.locked ? that.Validators.required : null));&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        var name = &#039;profile_field_&#039; + field.shortname;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            type: &amp;quot;text&amp;quot;,&lt;br /&gt;
            name: name,&lt;br /&gt;
            value: that.CoreTextUtilsProvider.cleanTags(formValues[name]),&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Translate dynamic strings===&lt;br /&gt;
If you wish to have an element that displays a localised string based on value from your template you can doing something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_myactivity.&amp;lt;% status %&amp;gt;&#039; | translate }}&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This could save you from having to write something like when only one value should be displayed:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        &amp;lt;%#isedting%&amp;gt;{{ &#039;plugin.mod_myactivity.editing&#039; | translate }}&amp;lt;%/isediting%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isopen%&amp;gt;{{ &#039;plugin.mod_myactivity.open&#039; | translate }}&amp;lt;%/isopen%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isclosed%&amp;gt;{{ &#039;plugin.mod_myactivity.closed&#039; | translate }}&amp;lt;%/isclosed%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using strings with dates===&lt;br /&gt;
If you have a string that you wish to pass a formatted date, for example in the Moodle language file you have:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;strwithdate&#039;] = &#039;This string includes a date of {$a-&amp;gt;date} in the middle of it.&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can localise the string correctly in your template using something like the following:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{ &#039;plugin.mod_myactivity.strwithdate&#039; | translate: {$a: { date: &amp;lt;% timestamp %&amp;gt; * 1000 | coreFormatDate: &amp;quot;dffulldate&amp;quot; } } }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A Unix timestamp must be multiplied by 1000 as the Mobile App expects millisecond timestamps, whereas Unix timestamps are in seconds.&lt;br /&gt;
===Support push notification clicks===&lt;br /&gt;
If your plugin sends push notifications to the app, you might want to open a certain page in the app when the notification is clicked. There are several ways to achieve this.&lt;br /&gt;
&lt;br /&gt;
The easiest way is to include a &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; in your notification. When the notification is clicked, the app will try to open the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; will also be displayed in web. If you want to use a specific URL for the app, different than the one displayed in web, you can do so by returning a &amp;lt;code&amp;gt;customdata&amp;lt;/code&amp;gt; array that contains an &amp;lt;code&amp;gt;appurl&amp;lt;/code&amp;gt; property:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$notification-&amp;gt;customdata = [&lt;br /&gt;
    &#039;appurl&#039; =&amp;gt; $myurl-&amp;gt;out(),&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In both cases you will have to create a link handler to treat the URL. For more info on how to create the link handler, please see [[#Advanced_link_handler|how to create an advanced link handler]].&lt;br /&gt;
&lt;br /&gt;
If you want to do something that only happens when the notification is clicked, not when the link is clicked, you&#039;ll have to implement a push click handler yourself. The way to create it is similar to [[#Advanced_link_handler|creating an advanced link handler]], but you&#039;ll have to use &amp;lt;code&amp;gt;CorePushNotificationsDelegate&amp;lt;/code&amp;gt; and your handler will have to implement the properties and functions defined in the [https://github.com/moodlehq/moodleapp/blob/master/src/core/features/pushnotifications/services/push-delegate.ts#L27 CorePushNotificationsClickHandler] interface.&lt;br /&gt;
===Implement a module similar to mod_label===&lt;br /&gt;
In Moodle 3.8 or higher, if your plugin doesn&#039;t support &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt; and you don&#039;t specify a &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; then the module will only display the module description in the course page and it won&#039;t be clickable in the app, just like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt;. You can decide if you want the module icon to be displayed or not (if you don&#039;t want it to be displayed, then don&#039;t define it in &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
However, if your plugin needs to work in previous versions of Moodle or you want to display something different than the description then you need a different approach.&lt;br /&gt;
&lt;br /&gt;
If your plugin wants to render something in the course page instead of just the module name and description you should specify the &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. The template returned by this method will be rendered in the course page. Please notice the HTML returned should not contain directives or components, only plain HTML.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want your module to be clickable then you just need to remove &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. With these 2 changes you can have a module that behaves like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt; in the app.&lt;br /&gt;
===Use Ionic navigation lifecycle functions===&lt;br /&gt;
Ionic let pages define some functions that will be called when certain navigation lifecycle events happen. For more info about these functions, see [https://ionicframework.com/docs/api/router-outlet Ionic&#039;s documentation].&lt;br /&gt;
&lt;br /&gt;
You can define these functions in your plugin javascript:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.ionViewWillLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In addition to that, you can also implement &amp;lt;code&amp;gt;canLeave&amp;lt;/code&amp;gt; to use Angular route guards:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.canLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So for example you can make your plugin ask for confirmation if the user tries to leave the page when he has some unsaved data.&lt;br /&gt;
===Module plugins: dynamically determine if a feature is supported===&lt;br /&gt;
In Moodle you can specify if your plugin supports a certain feature, like &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. If your plugin will always support or not a certain feature, then you can use the &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; to specify it ([[#Options_only_for_CoreCourseModuleDelegate|see more documentation about this]]). But if you need to calculate it dynamically then you will have to create a function to calculate it. &lt;br /&gt;
&lt;br /&gt;
This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section above). The JavaScript returned by your initialisation method will need to define a function named &amp;lt;code&amp;gt;supportsFeature&amp;lt;/code&amp;gt; that will receive the name of the feature:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    supportsFeature: function(featureName) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;.&lt;br /&gt;
== Testing ==&lt;br /&gt;
You can also write automated tests for your plugin using Behat, you can read more about it on the [[Acceptance testing for the Moodle App]] page.&lt;br /&gt;
== Upgrading plugins from an older version ==&lt;br /&gt;
If you added mobile support to your plugin for the Ionic 3 version of the app (previous to the 3.9.5 release), you will probably need to make some changes to make it compatible with Ionic 5.&lt;br /&gt;
&lt;br /&gt;
Learn more at the [[Moodle App Plugins Upgrade Guide]].&lt;br /&gt;
==Troubleshooting==&lt;br /&gt;
=== Invalid response received ===&lt;br /&gt;
You might receive this error when using the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive or similar. By default, the app expects all Web Service calls to return an object, if your Web Service returns another type (string, boolean, etc.) then you need to specify it using the &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; attribute of the directive. For example, if your WS returns a boolean value, then you should specify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{typeExpected: &#039;boolean&#039;}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In a similar way, if your Web Service returns &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; you need to tell the app not to expect any result using &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{responseExpected: false}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS ===&lt;br /&gt;
Some directives allow you to specify a form id or name to send the data from the form to a certain WS. These directives look for HTML inputs to retrieve the data to send. However, &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; don&#039;t use HTML inputs, they simulate them, so the directive isn&#039;t going to find their data and so it won&#039;t be sent to the Web Service.&lt;br /&gt;
&lt;br /&gt;
There are 2 workarounds to fix this problem.&lt;br /&gt;
==== Sending the data manually ====&lt;br /&gt;
The first solution is to send the missing params manually using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; property. We will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input value in a variable, and this variable will be passed to the parameters. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a template like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group name=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, responses: responses}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Basically, you need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to the affected element (in this case, the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt;). You can put whatever name you want as the value, we used &amp;quot;responses&amp;quot;. With this, every time the user selects a radio button the value will be stored in a variable called &amp;quot;responses&amp;quot;. Then, in the button we are passing this variable to the parameters of the Web Service.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute has priority over &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;, so if you have an input with &amp;lt;code&amp;gt;name=&amp;quot;responses&amp;quot;&amp;lt;/code&amp;gt; it will override what you&#039;re manually passing to &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;.&lt;br /&gt;
==== Using a hidden input ====&lt;br /&gt;
Since the directive is looking for HTML inputs, you need to add one with the value to send to the server. You can use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to synchronise your radio/checkbox/select with the new hidden input. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a radio button like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot; [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;hidden&amp;quot; [ngModel]=&amp;quot;responses&amp;quot; name=&amp;quot;responses&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we&#039;re using a variable called &amp;quot;responses&amp;quot; to synchronise the data between the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt; and the hidden input. You can use whatever name you want.&lt;br /&gt;
=== I can&#039;t return an object or array in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; ===&lt;br /&gt;
If you try to return an object or an array in any field inside &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, the Web Service call will fail with the following error:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Scalar type expected, array or object received&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Each field in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; must be a string, number or boolean; it cannot be an object or array. To make it work, you need to encode your object or array into a JSON string:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($data)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app will automatically parse this JSON and convert it back into an array or object.&lt;br /&gt;
==Examples==&lt;br /&gt;
===Accepting dynamic names in a Web Service===&lt;br /&gt;
We want to display a form where the names of the fields are dynamic, like it happens in quiz. This data will be sent to a new Web Service that we have created.&lt;br /&gt;
&lt;br /&gt;
The first issue we find is that the Web Service needs to define the names of the parameters received, but in this case they&#039;re dynamic. The solution is to accept an array of objects with name and value. So in the &amp;lt;code&amp;gt;_parameters()&amp;lt;/code&amp;gt; function of our new Web Service, we will add this parameter:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;data&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
     new external_single_structure(&lt;br /&gt;
        [&lt;br /&gt;
            &#039;name&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data name&#039;),&lt;br /&gt;
            &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data value&#039;),&lt;br /&gt;
        ]&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;The data to be saved&#039;, VALUE_DEFAULT, []&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now we need to adapt our form to send the data as the Web Service requires it. In our template, we have a button with the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive that will send the form data to our Web Service. To make this work we will have to pass the parameters manually, without using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute, because we need to format the data before it is sent.&lt;br /&gt;
&lt;br /&gt;
Since we will send the parameters manually and we want it all to be sent in the same array, we will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input data into a variable that we&#039;ll call &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;, but you can use the name you want. This variable will be an object that will hold the input data with the format &amp;quot;name-&amp;gt;value&amp;quot;. For example, if I have an input with name &amp;quot;a1&amp;quot; and value &amp;quot;My answer&amp;quot;, the data object will be:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{a1: &#039;My answer&#039;}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So we need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to all the inputs whose values need to be sent to the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; WS param. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-input name=&amp;quot;&amp;lt;% name %&amp;gt;&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.data[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re using &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; to store the data. We do it like this because we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the form, setting the values the user has already stored. If you don&#039;t need to initialise the form, then you can use the &amp;lt;code&amp;gt;dataObject&amp;lt;/code&amp;gt; variable, an empty object that the mobile app creates for you:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[(ngModel)]=&amp;quot;dataObject[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app has a function that allows you to convert this data object into an array like the one the WS expects: &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt;. So in our button we&#039;ll use this function to format the data before it&#039;s sent:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_ws_name&amp;quot;&lt;br /&gt;
    [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, data: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.data, &#039;name&#039;, &#039;value&#039;)}&amp;quot;&lt;br /&gt;
    successMessage&lt;br /&gt;
    refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see in the example above, we&#039;re specifying that the keys of the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object need to be stored in a property called &amp;quot;name&amp;quot;, and the values need to be stored in a property called &amp;quot;value&amp;quot;. If your Web Service expects different names you need to change the parameters of the &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
If you open your plugin now in the app it will display an error in the JavaScript console. The reason is that the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; variable doesn&#039;t exist inside &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. As it is explained in previous sections, &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; holds the data that you return in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; for your method. We&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the values to be displayed in the form.&lt;br /&gt;
&lt;br /&gt;
If the user hasn&#039;t answered the form yet, we can initialise the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object as an empty object. Please remember that we cannot return arrays or objects in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, so we&#039;ll return a JSON string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; &#039;{}&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
With the code above, the form will always be empty when the user opens it. But now we want to check if the user has already answered the form and fill the form with the previous values. We will do it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$userdata = get_user_responses(); // It will held the data in a format name-&amp;gt;value. Example: [&#039;a1&#039; =&amp;gt; &#039;My value&#039;].&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($userdata)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now the user will be able to see previous values when the form is opened, and clicking the button will send the data to our Web Service in array format.&lt;br /&gt;
==Moodle plugins with mobile support==&lt;br /&gt;
* Group choice: [https://moodle.org/plugins/mod_choicegroup Moodle plugins directory entry] and [https://github.com/ndunand/moodle-mod_choicegroup code in github].&lt;br /&gt;
* Custom certificate: [https://moodle.org/plugins/mod_customcert Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_customcert code in github].&lt;br /&gt;
* Gapfill question type: [https://moodle.org/plugins/qtype_gapfill Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_gapfill in github].&lt;br /&gt;
* Wordselect question type: [https://moodle.org/plugins/qtype_wordselect Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_wordselect in github].&lt;br /&gt;
* RegExp question type: [https://moodle.org/plugins/qtype_regexp Moodle plugins directory entry] and [https://github.com/rezeau/moodle-qtype_regexp in github].&lt;br /&gt;
* Certificate: [https://moodle.org/plugins/mod_certificate Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_certificate in github].&lt;br /&gt;
* Attendance [https://moodle.org/plugins/mod_attendance Moodle plugins directory entry] and [https://github.com/danmarsden/moodle-mod_attendance in github].&lt;br /&gt;
* ForumNG (unfinished support) [https://moodle.org/plugins/mod_forumng Moodle plugins directory entry] and [https://github.com/moodleou/moodle-mod_forumng in github].&lt;br /&gt;
* News block [https://github.com/moodleou/moodle-block_news in github].&lt;br /&gt;
* H5P activity module [https://moodle.org/plugins/mod_hvp Moodle plugins directory entry] and [https://github.com/h5p/h5p-moodle-plugin in github].&lt;br /&gt;
See the complete list in [https://moodle.org/plugins/browse.php?list=award&amp;amp;id=6 the plugins database] (it may contain some outdated plugins).&lt;br /&gt;
=== Mobile app support award ===&lt;br /&gt;
If you want your plugin to be awarded in the plugins directory and marked as supporting the mobile app, please feel encouraged to contact us via email at [mailto:mobile@moodle.com mobile@moodle.com].&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to include a link to your plugin page and the location of its code repository.&lt;br /&gt;
&lt;br /&gt;
See [https://moodle.org/plugins/?q=award:mobile-app the list of awarded plugins] in the plugins directory.&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Moodle App Ionic 5]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62056</id>
		<title>Moodle App Plugins Development Guide</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62056"/>
		<updated>2022-04-25T08:17:33Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: /* Options only for CoreCourseModuleDelegate */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle App (Ionic 5)}}&lt;br /&gt;
If you want to add mobile support to your Moodle plugin, you can achieve it by extending different areas of the app using &#039;&#039;just PHP server side code&#039;&#039; and providing templates written with [https://ionicframework.com/docs/components Ionic] and custom components.&lt;br /&gt;
&lt;br /&gt;
You will have to:&lt;br /&gt;
# Create a &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file in your plugin. In this file, you will be able to indicate which areas of the app you want to extend. For example, adding a new option in the main menu, implementing support for a new activity module, including a new option in the course menu, including a new option in the user profile, etc. All the areas supported are described further in this document.&lt;br /&gt;
# Create new functions in a reserved namespace that will return the content of the new options. The content should be returned rendered as html. This html should use Ionic components so that it looks native, but it can be generated using mustache templates.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let’s clarify some points:&lt;br /&gt;
* You don’t need to create new Web Service functions (although you will be able to use them for advanced features). You just need plain php functions that will be placed in a reserved namespace.&lt;br /&gt;
* Those functions will be exported via the Web Service function &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt;.&lt;br /&gt;
* As arguments of your functions you will always receive the &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, some relevant details of the app (like the app version or the current language in the app), and some specific data depending on the type of plugin (&amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
* The mobile app also implements a list of custom Ionic components and directives that provide dynamic behaviour; like indicating that you are linking a file that can be downloaded, allowing a transition to new pages into the app calling a specific function in the server, submitting form data to the server, etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Getting started==&lt;br /&gt;
If you only want to write a plugin, it is not necessary that you set up your environment to work with the Moodle App. In fact, you don&#039;t even need to compile it. You can just [[Using the Moodle App in a browser|use a Chromium-based browser]] to add mobile support to your plugins!&lt;br /&gt;
&lt;br /&gt;
You can use the app from one of the hosted versions on [https://master.apps.moodledemo.net master.apps.moodledemo.net] (the latest stable version) and [https://integration.apps.moodledemo.net integration.apps.moodledemo.net] (the latest development version). If you need any specific environment (hosted versions are deployed with a &#039;&#039;production&#039;&#039; environment), you can also use [[Moodle App Docker Images|Docker images]]. And if you need to test your plugin in a native device, you can always use [https://download.moodle.org/mobile Moodle HQ&#039;s application].&lt;br /&gt;
&lt;br /&gt;
This should suffice for developing plugins. However, if you are working on advanced functionality and you need to run the application from the source code, you can find more information in the [[Moodle App Development Guide]].&lt;br /&gt;
===Development workflow===&lt;br /&gt;
Before getting into the specifics of your plugin, we recommend that you start adding a simple &amp;quot;Hello World&amp;quot; button in the app to see that everything works properly.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s say your plugin is called &amp;lt;code&amp;gt;local_hello&amp;lt;/code&amp;gt;, you can start by adding the following files:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;local_hello&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [&lt;br /&gt;
            &#039;hello&#039; =&amp;gt; [&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreMainMenuDelegate&#039;,&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;view_hello&#039;,&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;title&#039; =&amp;gt; &#039;hello&#039;,&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; &#039;earth&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [&lt;br /&gt;
            [&#039;hello&#039;, &#039;local_hello&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace local_hello\output;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    public static function view_hello() {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; &#039;&amp;lt;h1 class=&amp;quot;text-center&amp;quot;&amp;gt;{{ &amp;quot;plugin.local_hello.hello&amp;quot; | translate }}&amp;lt;/h1&amp;gt;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;lang/en/local_hello.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;hello&#039;] = &#039;Hello World&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once you&#039;ve done that, try logging into your site in the app and you should see a new button in the main menu or more menu (depending on the device) saying &amp;quot;Hello World&amp;quot;. If you press this button, you should see a page saying &amp;quot;Hello World!&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Congratualtions, you have written your first Moodle plugin with moodle support!&lt;br /&gt;
&lt;br /&gt;
You can read the rest of this page to learn more about mobile plugins and start working on your plugin. Here&#039;s some things to keep in mind:&lt;br /&gt;
* If you change the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file, you will have to refresh the browser. And remember to [https://developer.chrome.com/docs/devtools/network/reference/#disable-cache disable the network cache].&lt;br /&gt;
* If you change an existing template or function, you won’t have to refresh the browser. In most cases, doing a PTR (Pull To Refresh) in the page that displays the template will suffice.&lt;br /&gt;
* If any of these doesn&#039;t show your changes, you may need to [https://docs.moodle.org/311/en/Developer_tools#Purge_all_caches purge all caches] to avoid problems with the auto-loading cache.&lt;br /&gt;
* Ultimately, if that doesn&#039;t work either, you may have to log out from the site and log in again. If any changes affect plugin installation, you may also need to increase the version in your plugin&#039;s &amp;lt;code&amp;gt;version.php&amp;lt;/code&amp;gt; file and upgrade it in the site.&lt;br /&gt;
==Types of plugins==&lt;br /&gt;
There are 3 types of plugins:&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
[[File:Templates_downloaded_when_requested.png|thumb]]&lt;br /&gt;
With this type of plugin, the template of your plugin will be generated and downloaded when the user opens the plugin in the app. This means that your function will receive some context parameters. For example, if you&#039;re developing a course module plugin you will receive the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; (course module ID). You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
[[File:Templates_downloaded_on_login.png|thumb]]&lt;br /&gt;
With this type of plugin, the template for your plugin will be downloaded when the user logs in into the app and will be stored in the device. This means that your function will not receive any context parameters, and you need to return a generic template that will be built with JS data like the ones in the Moodle App. When the user opens a page that includes your plugin, your template will receive the required JS data and your template will be rendered. You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
You can always implement the whole plugin yourself using JavaScript instead of using our API. In fact, this is required if you want to implement some features like capturing links in the Mobile app. You can see the list of delegates that only support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
==Step by step example==&lt;br /&gt;
In this example, we are going to update an existing plugin, the [https://github.com/mdjnelson/moodle-mod_certificate Certificate activity module], that previously used a [[Moodle Mobile 2 (Ionic 1) Remote add-ons|Remote add-on]] (a legacy approach to implement mobile plugins).&lt;br /&gt;
&lt;br /&gt;
This is a simple activity module that displays the certificate issued for the current user along with the list of the dates of previously issued certificates. It also stores in the course log that the user viewed a certificate. This module also works offline: when the user downloads the course or activity, the data is pre-fetched and can be viewed offline.&lt;br /&gt;
===Step 1. Update the &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file===&lt;br /&gt;
In this case, we are updating an existing file. For new plugins, you should create this new file.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;mod_certificate&#039; =&amp;gt; [ // Plugin identifier&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;coursecertificate&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/mod/certificate/pix/icon.gif&#039;,&lt;br /&gt;
                    &#039;class&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
       &lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseModuleDelegate&#039;, // Delegate (where to display the link to the plugin)&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;mobile_course_view&#039;, // Main function in \mod_certificate\output\mobile&lt;br /&gt;
                &#039;offlinefunctions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;mobile_course_view&#039; =&amp;gt; [],&lt;br /&gt;
                    &#039;mobile_issues_view&#039; =&amp;gt; [],&lt;br /&gt;
                ], // Function that needs to be downloaded for offline.&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [ // Language strings that are used in all the handlers.&lt;br /&gt;
            [&#039;pluginname&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;summaryofattempts&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;getcertificate&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;requiredtimenotmet&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;viewcertificateviews&#039;, &#039;certificate&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Plugin identifier:&lt;br /&gt;
: A unique name for the plugin, it can be anything (there’s no need to match the module name).&lt;br /&gt;
&lt;br /&gt;
;Handlers (Different places where the plugin will display content):&lt;br /&gt;
: A plugin can be displayed in different views in the app. Each view should have a unique name inside the plugin scope (alphanumeric).&lt;br /&gt;
&lt;br /&gt;
; Display data:&lt;br /&gt;
: This is only needed for certain types of plugins. Also, depending on the type of delegate it may require additional (or less fields). In this case, we are indicating the module icon.&lt;br /&gt;
&lt;br /&gt;
; Delegate&lt;br /&gt;
: Where to display the link to the plugin, see the [[#Delegates|Delegates]] section for all the possible options.&lt;br /&gt;
&lt;br /&gt;
; Method:&lt;br /&gt;
: This is the method in the Moodle &amp;lt;code&amp;gt;\{component-name}\output\mobile&amp;lt;/code&amp;gt; class to be executed the first time the user clicks in the new option displayed in the app.&lt;br /&gt;
&lt;br /&gt;
; Offlinefunctions&lt;br /&gt;
: This is the list of functions that need to be called and stored when the user downloads a course for offline usage. Please note that you can add functions here that are not even listed in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. &lt;br /&gt;
: In our example, downloading for offline access will mean that we&#039;ll execute the functions for getting the certificate and issued certificates passing as parameters the current &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; (and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; when we are using the mod or course delegate). If we have the result of those functions stored in the app, we&#039;ll be able to display the certificate information even if the user is offline.&lt;br /&gt;
: Offline functions will be mostly used to display information for final users, any further interaction with the view won’t be supported offline (for example, trying to send information when the user is offline).&lt;br /&gt;
: You can indicate here other Web Services functions, indicating the parameters that they might need from a defined subset (currently &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Prefetching the module will also download all the files returned by the methods in these offline functions (in the &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; array).&lt;br /&gt;
: Note that if your functions use additional custom parameters (for example, if you implement multiple pages within a module&#039;s view function by using a &amp;lt;code&amp;gt;page&amp;lt;/code&amp;gt; parameter in addition to the usual &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;) then the app will not know which additional parameters to supply. In this case, do not list the function in &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt;; instead, you will need to manually implement a [[#Module_prefetch_handler|module prefetch handler]].&lt;br /&gt;
&lt;br /&gt;
;Lang:&lt;br /&gt;
: The language pack string ids used in the plugin by all the handlers. Normally these will be strings from your own plugin, however, you can list any strings you need here, like &amp;lt;code&amp;gt;[&#039;cancel&#039;, &#039;moodle&#039;]&amp;lt;/code&amp;gt;. If you do this, be warned that in the app you will then need to refer to that string as &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.myplugin.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (not &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.moodle.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Please only include the strings you actually need. The Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform, and this will then be cached, so listing too many strings is very wasteful.&lt;br /&gt;
There are additional attributes supported by the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; list, you can find about them in the [[#Mobile.php_supported_options|Mobile.php supported options]] section.&lt;br /&gt;
===Step 2. Creating the main function===&lt;br /&gt;
The main function displays the current issued certificate (or several warnings if it’s not possible to issue a certificate). It also displays a link to view the dates of previously issued certificates.&lt;br /&gt;
&lt;br /&gt;
All the functions must be created in the plugin or subsystem &amp;lt;code&amp;gt;classes/output&amp;lt;/code&amp;gt; directory, the name of the class must be &amp;lt;code&amp;gt;mobile&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
For this example, the namespace name will be &amp;lt;code&amp;gt;mod_certificate\output&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace mod_certificate\output;&lt;br /&gt;
&lt;br /&gt;
use context_module;&lt;br /&gt;
use mod_certificate_external;&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Returns the certificate course view for the mobile app.&lt;br /&gt;
     *&lt;br /&gt;
     * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
     *&lt;br /&gt;
     * @return array       HTML, JS and other data.&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
        $args = (object) $args;&lt;br /&gt;
        $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
        // Capabilities check.&lt;br /&gt;
        require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
        $context = \context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
        require_capability(&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
        if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
            require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
        }&lt;br /&gt;
        $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
        // Get certificates from external (taking care of exceptions).&lt;br /&gt;
        try {&lt;br /&gt;
            $issued = \mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
            $certificates = \mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
            $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $issues = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Set timemodified for each certificate.&lt;br /&gt;
        foreach ($issues as $issue) {&lt;br /&gt;
            if (empty($issue-&amp;gt;timemodified)) {&lt;br /&gt;
                $issue-&amp;gt;timemodified = $issue-&amp;gt;timecreated;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $showget = true;&lt;br /&gt;
        if ($certificate-&amp;gt;requiredtime &amp;amp;&amp;amp; !has_capability(&#039;mod/certificate:manage&#039;, $context)) {&lt;br /&gt;
            if (certificate_get_course_time($certificate-&amp;gt;course) &amp;lt; ($certificate-&amp;gt;requiredtime * 60)) {&lt;br /&gt;
                $showget = false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $certificate-&amp;gt;name = format_string($certificate-&amp;gt;name);&lt;br /&gt;
        [$certificate-&amp;gt;intro, $certificate-&amp;gt;introformat] =&lt;br /&gt;
                external_format_text($certificate-&amp;gt;intro, $certificate-&amp;gt;introformat, $context-&amp;gt;id, &#039;mod_certificate&#039;, &#039;intro&#039;);&lt;br /&gt;
        $data = [&lt;br /&gt;
            &#039;certificate&#039; =&amp;gt; $certificate,&lt;br /&gt;
            &#039;showget&#039; =&amp;gt; $showget &amp;amp;&amp;amp; count($issues) &amp;gt; 0,&lt;br /&gt;
            &#039;issues&#039; =&amp;gt; $issues,&lt;br /&gt;
            &#039;issue&#039; =&amp;gt; $issues[0],&lt;br /&gt;
            &#039;numissues&#039; =&amp;gt; count($issues),&lt;br /&gt;
            &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
            &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;files&#039; =&amp;gt; $issues,&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Function declaration: &lt;br /&gt;
: The function name is the same as the one used in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file (&amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; field). There is only one argument, &amp;lt;code&amp;gt;$args&amp;lt;/code&amp;gt;, which is an array containing all the information sent by the mobile app (the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversionname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversioncode&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;applang&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appcustomurlscheme&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
&lt;br /&gt;
; Function implementation:&lt;br /&gt;
: In the first part of the function, we check permissions and capabilities (like a &amp;lt;code&amp;gt;view.php&amp;lt;/code&amp;gt; script would do normally). Then we retrieve the certificate information that’s necessary to display the template.&lt;br /&gt;
&lt;br /&gt;
; Function return:&lt;br /&gt;
* &amp;lt;code&amp;gt;templates&amp;lt;/code&amp;gt; — The rendered template (notice that we could return more than one template, but we usually would only need one). By default the app will always render the first template received, the rest of the templates can be used if the plugin defines some JavaScript code.&lt;br /&gt;
* &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; — Empty, because we don’t need any in this case.&lt;br /&gt;
* &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; — Empty as well, because we don’t need any additional data to be used by directives or components in the template. This field will be published as an object supporting 2-way data-binding in the template.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; — A list of files that the app should be able to download (for offline usage mostly).&lt;br /&gt;
===Step 3. Creating the template for the main function===&lt;br /&gt;
This is the most important part of your plugin because it contains the code that will be rendered on the mobile app.&lt;br /&gt;
&lt;br /&gt;
In this template we’ll be using Ionic, together with directives and components specific to the Moodle App.&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;ion-&amp;lt;/code&amp;gt; are ionic components. Most of the time, the component name is self-explanatory but you may refer to a detailed guide here: https://ionicframework.com/docs/components/&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;core-&amp;lt;/code&amp;gt; are custom components of the Moodle App.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_page.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;core-course-module-description description=&amp;quot;&amp;lt;% certificate.intro %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-course-module-description&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;ion-list-header&amp;gt;&lt;br /&gt;
            &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.summaryofattempts&#039; | translate }}&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-list-header&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-new-content title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; &lt;br /&gt;
                        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
                        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
                    {{ &#039;plugin.mod_certificate.viewcertificateviews&#039; | translate: {$a: &amp;lt;% numissues %&amp;gt;} }}&lt;br /&gt;
                &amp;lt;/ion-button&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&lt;br /&gt;
                        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
                        [files]=&amp;quot;[{&lt;br /&gt;
                            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
                            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
                            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
                        }]&amp;quot;&amp;gt;&lt;br /&gt;
                 &lt;br /&gt;
                        &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot; slot=&amp;quot;start&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                            {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
                    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%^showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;p&amp;gt;{{ &#039;plugin.mod_certificate.requiredtimenotmet&#039; | translate }}&amp;lt;/p&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;!-- Call log WS when the template is loaded. --&amp;gt;&lt;br /&gt;
        &amp;lt;span core-site-plugins-call-ws-on-load name=&amp;quot;mod_certificate_view_certificate&amp;quot;&lt;br /&gt;
                [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/span&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache).&lt;br /&gt;
&lt;br /&gt;
Then we display the module description using &amp;lt;code&amp;gt;core-course-module-description&amp;lt;/code&amp;gt;, which is a component used to include the course module description.&lt;br /&gt;
&lt;br /&gt;
For displaying the certificate information we create a list of elements, adding a header on top.&lt;br /&gt;
&lt;br /&gt;
The following line using the &amp;lt;code&amp;gt;translate&amp;lt;/code&amp;gt; filter indicates that the app will translate the &amp;lt;code&amp;gt;summaryofattempts&amp;lt;/code&amp;gt; string id (here we could’ve used mustache translation but it is usually better to delegate the strings translations to the app). The string id has the following format: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
plugin.{plugin-identifier}.{string-id}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Where &amp;lt;code&amp;gt;{plugin-identifier}&amp;lt;/code&amp;gt; is taken from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;{string-id}&amp;lt;/code&amp;gt; must be indicated in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; field in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Then, we display a button to transition to another page if there are certificates issued. The attribute (directive) &amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt; indicates that if the user clicks the button, we need to call the &amp;lt;code&amp;gt;mobile_issues_view&amp;lt;/code&amp;gt; function in the &amp;lt;code&amp;gt;mod_certificate&amp;lt;/code&amp;gt; component; passing as arguments the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;. The content returned by this function will be displayed in a new page (read the following section to see the code of this new page).&lt;br /&gt;
&lt;br /&gt;
Just after this button, we display another one but this time for downloading an issued certificate. The &amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt; directive indicates that clicking this button is for downloading the whole activity and opening the main file. This means that, when the user clicks this button, the whole certificate activity will be available offline.&lt;br /&gt;
&lt;br /&gt;
Finally, just before the &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt; is closed, we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt; directive to indicate that once the page is loaded, we need to call a Web Service function in the server, in this case we are calling the &amp;lt;code&amp;gt;mod_certificate_view_certificate&amp;lt;/code&amp;gt; that will log that the user viewed this page.&lt;br /&gt;
&lt;br /&gt;
As you can see, no JavaScript was necessary at all. We used plain HTML elements and attributes that did all the complex dynamic logic (like calling a Web Service) behind the scenes.&lt;br /&gt;
&lt;br /&gt;
===Step 4. Adding an additional page===&lt;br /&gt;
Add the following method to &amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Returns the certificate issues view for the mobile app.&lt;br /&gt;
 * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
 *&lt;br /&gt;
 * @return array       HTML, JS and other data.&lt;br /&gt;
 */&lt;br /&gt;
public static function mobile_issues_view($args) {&lt;br /&gt;
    global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
    $args = (object) $args;&lt;br /&gt;
    $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
    // Capabilities check.&lt;br /&gt;
    require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
    $context = context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
    require_capability (&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
    if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
        require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
    }&lt;br /&gt;
    $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
    // Get certificates from external (taking care of exceptions).&lt;br /&gt;
    try {&lt;br /&gt;
        $issued = mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
        $certificates = mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
        $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
    } catch (Exception $e) {&lt;br /&gt;
        $issues = [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $data = [&#039;issues&#039; =&amp;gt; $issues];&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
            [&lt;br /&gt;
                &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_issues&#039;, $data),&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
        &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This method for the new page was added just after &amp;lt;code&amp;gt;mobile_course_view&amp;lt;/code&amp;gt;, the code is quite similar: checks the capabilities, retrieves the information required for the template, and returns the template rendered.&lt;br /&gt;
&lt;br /&gt;
The code of the mustache template is also very simple.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_issues.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &amp;lt;%timecreated%&amp;gt; | coreToLocaleString }}&amp;lt;/p&amp;gt;&lt;br /&gt;
                &amp;lt;p&amp;gt;&amp;lt;%grade%&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As we did in the previous template, in the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache). &lt;br /&gt;
&lt;br /&gt;
Here we are creating an Ionic list that will display a new item in the list per each issued certificated.&lt;br /&gt;
&lt;br /&gt;
For the issued certificated we’ll display the time when it was created (using the app filter &amp;lt;code&amp;gt;coreToLocaleString&amp;lt;/code&amp;gt;). We are also displaying the grade displayed in the certificate (if any).&lt;br /&gt;
===Step 5. Plugin webservices, if included===&lt;br /&gt;
If your plugin uses its own web services, they will also need to be enabled for mobile access in your &amp;lt;code&amp;gt;db/services.php&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
The following line should be included in each webservice definition:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;services&#039; =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/db/services.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$functions = [&lt;br /&gt;
    &#039;mod_certificate_get_certificates_by_courses&#039; =&amp;gt; [&lt;br /&gt;
        &#039;classname&#039;     =&amp;gt; &#039;mod_certificate_external&#039;,&lt;br /&gt;
        &#039;methodname&#039;    =&amp;gt; &#039;get_certificates_by_courses&#039;,&lt;br /&gt;
        &#039;description&#039;   =&amp;gt; &#039;Returns a list of certificate instances...&#039;,&lt;br /&gt;
        &#039;type&#039;          =&amp;gt; &#039;read&#039;,&lt;br /&gt;
        &#039;capabilities&#039;  =&amp;gt; &#039;mod/certificate:view&#039;,&lt;br /&gt;
        &#039;services&#039;      =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Mobile.php supported options==&lt;br /&gt;
In the previous section, we learned about some of the existing options for handlers configuration. This is the full list of supported options.&lt;br /&gt;
===Common options===&lt;br /&gt;
* &amp;lt;code&amp;gt;delegate&amp;lt;/code&amp;gt; (mandatory) — Name of the delegate to register the handler in.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (mandatory) — The method to call to retrieve the main page content.&lt;br /&gt;
* &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; (optional) — A method to call to retrieve the initialisation JS and the restrictions to apply to the whole handler. It can also return templates that can be used from JavaScript. You can learn more about this in the [[#Initialisation|Initialisation]] section.&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttocurrentuser&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForUser&amp;lt;/code&amp;gt; function. If true, the handler will only be shown for the current user. For more info about displaying the plugin only for certain users, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttoenrolledcourses&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForCourse&amp;lt;/code&amp;gt; function. If true or not defined, the handler will only be shown for courses the user is enrolled in. For more info about displaying the plugin only for certain courses, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;styles&amp;lt;/code&amp;gt; (optional) — An array with two properties: &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt;. The URL should point to a CSS file, either using an absolute URL or a relative URL. This file will be downloaded and applied by the app. It&#039;s recommended to include styles that will only affect your plugin templates. The version number is used to determine if the file needs to be downloaded again, you should change the version number everytime you change the CSS file.&lt;br /&gt;
* &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; (optional) — If your plugin supports a component in the app different than the one defined by your plugin, you can use this property to specify it. For example, you can create a local plugin to support a certain course format, activity, etc. The component of your plugin in Moodle would be &amp;lt;code&amp;gt;local_whatever&amp;lt;/code&amp;gt;, but in &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; you can specify that this handler will implement &amp;lt;code&amp;gt;format_whatever&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;mod_whatever&amp;lt;/code&amp;gt;. This property was introduced in the version 3.6.1 of the app.&lt;br /&gt;
===Options only for CoreMainMenuDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. Main Menu plugins are always displayed in the More tab, they cannot be displayed as tabs in the bottom bar.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreMainMenuHomeDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseOptionsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ismenuhandler&amp;lt;/code&amp;gt; (optional) — Supported from the 3.7.1 version of the app. Set it to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if you want your plugin to be displayed in the contextual menu of the course instead of in the top tabs. The contextual menu is displayed when you click in the 3-dots button at the top right of the course.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseModuleDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — Path to the module icon. After Moodle app 4.0, this icon is only used as a fallback, the app will always try to use the theme icon so themes can override icons in the app.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (optional) — The function to call to retrieve the main page content. In this delegate the method is optional. If the method is not set, the module won&#039;t be clickable.&lt;br /&gt;
* &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt; (optional) — List of functions to call when prefetching the module. It can be a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; method or a WS. You can filter the params received by the WS. By default, WS will receive these params: &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;. Other valid values that will be added if they are present in the list of params: &amp;lt;code&amp;gt;courseids&amp;lt;/code&amp;gt; (it will receive a list with the courses the user is enrolled in), &amp;lt;code&amp;gt;{component}id&amp;lt;/code&amp;gt; (For example, &amp;lt;code&amp;gt;certificateid&amp;lt;/code&amp;gt;).&lt;br /&gt;
* &amp;lt;code&amp;gt;downloadbutton&amp;lt;/code&amp;gt; (optional) — Whether to display download button in the module. If not defined, the button will be shown if there is any offlinefunction.&lt;br /&gt;
* &amp;lt;code&amp;gt;isresource&amp;lt;/code&amp;gt; (optional) — Whether the module is a resource or an activity. Only used if there is any offline function. If your module relies on the &amp;lt;code&amp;gt;contents&amp;lt;/code&amp;gt; field, then it should be &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;updatesnames&amp;lt;/code&amp;gt; (optional) — Only used if there is any offline function. A regular expression to check if there&#039;s any update in the module. It will be compared to the result of &amp;lt;code&amp;gt;core_course_check_updates&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayopeninbrowser&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Open in browser&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayOpenInBrowser = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydescription&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Description&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayDescription = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayrefresh&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Refresh&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayRefresh = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayprefetch&amp;lt;/code&amp;gt; (optional) — Whether the module should display the download option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayPrefetch = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysize&amp;lt;/code&amp;gt; (optional) — Whether the module should display the downloaded size in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displaySize = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; (optional) — It can be used to specify the supported features of the plugin. Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. It should be an array with features as keys (For example, &amp;lt;code&amp;gt;[FEATURE_NO_VIEW_LINK =&amp;gt; true&amp;lt;/code&amp;gt;). If you need to calculate this dynamically please see [[#Module_plugins:_dynamically_determine_if_a_feature_is_supported|Module plugins: dynamically determine if a feature is supported]]. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; (optional) — If set, this method will be called when the course is rendered and the HTML returned will be displayed in the course page for the module. Please notice the HTML returned should not contain directives or components, only default HTML. Supported from the 3.8 version of the app.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreCourseFormatDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;canviewallsections&amp;lt;/code&amp;gt; (optional) — Whether the course format allows seeing all sections in a single page. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayenabledownload&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, it&#039;s no longer used.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysectionselector&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, use &#039;&#039;displaycourseindex&#039;&#039; instead.&lt;br /&gt;
*&amp;lt;code&amp;gt;displaycourseindex&amp;lt;/code&amp;gt; (optional) — Whether the default course index should be displayed. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
===Options only for CoreUserDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — The type of the addon. The values accepted are &amp;lt;code&amp;gt;&#039;newpage&#039;&amp;lt;/code&amp;gt; (default) and &amp;lt;code&amp;gt;&#039;communication&#039;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreSettingsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for AddonMessageOutputDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreBlockDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (optional):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section. If this is not supplied, it will default to &amp;lt;code&amp;gt;&#039;plugins.block_{block-name}.pluginname&#039;&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class. If this is not supplied, it will default to &amp;lt;code&amp;gt;block_{block-name}&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — Possible values are:&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;title&amp;quot;&amp;lt;/code&amp;gt; — Your block will only display the block title, and when it&#039;s clicked it will open a new page to display the block contents (the template returned by the block&#039;s method).&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;prerendered&amp;quot;&amp;lt;/code&amp;gt; — Your block will display the content and footer returned by the WebService to get the blocks (for example, &amp;lt;code&amp;gt;core_block_get_course_blocks&amp;lt;/code&amp;gt;), so your block&#039;s method will never be called.&lt;br /&gt;
*** Any other value — Your block will immediately call the method specified in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and it will use the template to render the block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fallback&amp;lt;/code&amp;gt; (optional) — This option allows you to specify a block to use in the app instead of your block. For example, you can make the app display the &amp;quot;My overview&amp;quot; block instead of your block in the app by setting &amp;lt;code&amp;gt;&#039;fallback&#039; =&amp;gt; &#039;myoverview&#039;&amp;lt;/code&amp;gt;. The fallback will only be used if you don&#039;t specify a &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; is different to &amp;lt;code&amp;gt;&#039;title&#039;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&#039;prerendered&#039;&amp;lt;/code&amp;gt;. Supported from the 3.9.0 version of the app.&lt;br /&gt;
==Delegates==&lt;br /&gt;
Delegates can be classified by type of plugin. For more info about type of plugins, please see the [[#Types_of_plugins|Types of plugins]] section.&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new items to the main menu (currently displayed at the bottom of the app). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuHomeDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new tabs in the home page (by default the app is displaying the &amp;quot;Dashboard&amp;quot; and &amp;quot;Site home&amp;quot; tabs). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new options in a course (Participants or Grades are examples of this type of delegate).&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting activity modules or resources.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add additional options in the user profile page in the app.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting course formats. When you open a course from the course list in the mobile app, it will check if there is a &amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt; handler for the format that site uses. If so, it will display the course using that handler. Otherwise, it will use the default app course format.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile course formats]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreSettingsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to add a new option in the settings page.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonMessageOutputDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a message output plugin.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreBlockDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a block. For example, blocks can be displayed in Site Home, Dashboard and the Course page.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question types.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile question types]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionBehaviourDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question behaviours.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting user profile fields.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModQuizAccessRuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a quiz access rule.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModAssignSubmissionDelegate&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;AddonModAssignFeedbackDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use these delegates to support assign submission or feedback plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonWorkshopAssessmentStrategyDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a workshop assessment strategy plugin.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
These delegates require JavaScript to be supported. See [[#Initialisation|Initialisation]] for more information.&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreContentLinksDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreCourseModulePrefetchDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFileUploaderDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CorePluginFileDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFilterDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
==Available components and directives==&lt;br /&gt;
===Difference between components and directives===&lt;br /&gt;
A directive is usually represented as an HTML attribute, allows you to extend a piece of HTML with additional information or functionality. Example of directives are: &amp;lt;code&amp;gt;core-auto-focus&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*ngIf&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ng-repeat&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components are also directives, but they are usually represented as an HTML tag and they are used to add custom elements to the app. Example of components are &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-item&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;core-search-box&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components and directives are Angular concepts; you can learn more about them and the components come out of the box with Ionic in the following links:&lt;br /&gt;
* [https://angular.io/guide/built-in-directives Angular directives documentation]&lt;br /&gt;
* [https://ionicframework.com/docs/components Ionic components]&lt;br /&gt;
===Custom core components and directives===&lt;br /&gt;
These are some useful custom components and directives that are only available in the Moodle App. Please note that this isn’t the full list of custom components and directives, it’s just an extract of the most common ones.&lt;br /&gt;
&lt;br /&gt;
You can find a full list of components and directives in the source code of the app, within [https://github.com/moodlehq/moodleapp/tree/master/src/core/components &amp;lt;code&amp;gt;src/core/components&amp;lt;/code&amp;gt;] and [https://github.com/moodlehq/moodleapp/tree/master/src/core/directives &amp;lt;code&amp;gt;src/core/directives&amp;lt;/code&amp;gt;].&lt;br /&gt;
====&amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;====&lt;br /&gt;
This directive formats the text and adds some directives needed for the app to work as it should. For example, it treats all links and all the embedded media so they work fine in the app. If some content in your template includes links or embedded media, please use this directive.&lt;br /&gt;
&lt;br /&gt;
This directive automatically applies &amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt; to all the links and embedded media.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;text&amp;lt;/code&amp;gt; (string) — The text to format.&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;adaptImg&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to adapt images to screen width. &lt;br /&gt;
* &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether all HTML tags should be removed.&lt;br /&gt;
* &amp;lt;code&amp;gt;singleLine&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether new lines should be removed to display all the text in single line. Only if &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; (number) — Optional. Max height in pixels to render the content box. The minimum accepted value is 50. Using this parameter will force &amp;lt;code&amp;gt;display: block&amp;lt;/code&amp;gt; to calculate the height better. If you want to avoid this, use &amp;lt;code&amp;gt;class=&amp;quot;inline&amp;quot;&amp;lt;/code&amp;gt; at the same time to use &amp;lt;code&amp;gt;display: inline-block&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;fullOnClick&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether it should open a new page with the full contents on click. Only if &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; is set and the content has been collapsed. &lt;br /&gt;
* &amp;lt;code&amp;gt;fullTitle&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;Description&amp;quot;&amp;lt;/code&amp;gt;. Title to use in full view.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-format-text text=&amp;quot;&amp;lt;% cm.description %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle a link. It performs several checks, like checking if the link needs to be opened in the app, and opens the link as it should (without overriding the app).&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;capture&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether the link needs to be captured by the app (check if the link can be handled by the app instead of opening it in a browser).&lt;br /&gt;
* &amp;lt;code&amp;gt;inApp&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to open in an embedded browser within the app or in the system browser.&lt;br /&gt;
* &amp;lt;code&amp;gt;autoLogin&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt;. If the link should be open with auto-login. Accepts the following values:&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;yes&amp;quot;&amp;lt;/code&amp;gt; — Always auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;no&amp;quot;&amp;lt;/code&amp;gt; — Never auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt; — Auto-login only if it points to the current site.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a href=&amp;quot;&amp;lt;% cm.url %&amp;gt;&amp;quot; core-link&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle links to files and embedded files. This directive should be used in any link to a file or any embedded file that you want to have available when the app is offline. &lt;br /&gt;
&lt;br /&gt;
If a file is downloaded, its URL will be replaced by the local file URL.&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;&amp;lt;% event.iconurl %&amp;gt;&amp;quot; core-external-content component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% event.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-user-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to go to user profile on click. When the user clicks the element where this directive is attached, the right user profile will be opened.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;userId&amp;lt;/code&amp;gt; (number) — User id to open the profile.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — Optional. Course id to show the user info related to that course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a ion-item core-user-link userId=&amp;quot;&amp;lt;% userid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to handle a remote file. It shows the file name, icon (depending on mime type) and a button to download or refresh it. The user can identify if the file is downloaded or not based on the button.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;file&amp;lt;/code&amp;gt; (object) — The file. Must have a &amp;lt;code&amp;gt;filename&amp;lt;/code&amp;gt; property and either &amp;lt;code&amp;gt;fileurl&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component the file belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDelete&amp;lt;/code&amp;gt; (boolean) — Optional. Whether the file can be deleted.&lt;br /&gt;
* &amp;lt;code&amp;gt;alwaysDownload&amp;lt;/code&amp;gt; (boolean) — Optional. Whether it should always display the refresh button when the file is downloaded. Use it for files that you cannot determine if they&#039;re outdated or not.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDownload&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether file can be downloaded. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-file&lt;br /&gt;
        [file]=&amp;quot;{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.url %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.name %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            filesize: &#039;&amp;lt;% issue.size %&amp;gt;&#039;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/core-file&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening a file. When the item with this directive is clicked, the file will be downloaded (if needed) and opened.&lt;br /&gt;
&lt;br /&gt;
It is usually recommended to use the &amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt; component since it also displays the state of the file.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt; (object) — The file to download.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. Component ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage (a button to download a file):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button&lt;br /&gt;
        [core-download-file]=&amp;quot;{&lt;br /&gt;
            fileurl: &amp;lt;% issue.url %&amp;gt;,&lt;br /&gt;
            timemodified: &amp;lt;% issue.timemodified %&amp;gt;,&lt;br /&gt;
            filesize: &amp;lt;% issue.size %&amp;gt;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.download | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening the main file of a module.&lt;br /&gt;
&lt;br /&gt;
When the item with this directive is clicked, the whole module will be downloaded (if needed) and its main file opened. This is meant for modules like &amp;lt;code&amp;gt;mod_resource&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This directive must receive either a &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; or a &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. If no files are provided, it will use &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; (object) — Optional, required if module is not supplied. The module object.&lt;br /&gt;
* &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt; (number) — Optional, required if module is not supplied. The module ID.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — The course ID the module belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional, defaults to the same value as &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. Component ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; (object[]) — Optional. List of files of the module. If not provided, uses &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot; &lt;br /&gt;
        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        [files]=&amp;quot;[{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
        }]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-navbar-buttons&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to add buttons to the app&#039;s header without having to place them inside the header itself. Using this component in a site plugin will allow adding buttons to the header of the current page.&lt;br /&gt;
&lt;br /&gt;
If this component indicates a position (start/end), the buttons will only be added if the header has some buttons in that position. If no start/end is specified, then the buttons will be added to the first &amp;lt;code&amp;gt;&amp;lt;ion-buttons&amp;gt;&amp;lt;/code&amp;gt; found in the header.&lt;br /&gt;
&lt;br /&gt;
You can use the &amp;lt;code&amp;gt;[hidden]&amp;lt;/code&amp;gt; input to hide all the inner buttons if a certain condition is met.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons end&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button (click)=&amp;quot;action()&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-icon slot=&amp;quot;icon-only&amp;quot; name=&amp;quot;funnel&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can also use this to add options to the context menu, for example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons&amp;gt;&lt;br /&gt;
    &amp;lt;core-context-menu&amp;gt;&lt;br /&gt;
        &amp;lt;core-context-menu-item&lt;br /&gt;
                [priority]=&amp;quot;500&amp;quot; content=&amp;quot;Nice boat&amp;quot; (action)=&amp;quot;boatFunction()&amp;quot;&lt;br /&gt;
                iconAction=&amp;quot;boat&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/core-context-menu-item&amp;gt;&lt;br /&gt;
    &amp;lt;/core-context-menu&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Using &#039;font&#039; icons with &amp;lt;code&amp;gt;ion-icon&amp;lt;/code&amp;gt;====&lt;br /&gt;
&lt;br /&gt;
Font icons are widely used on the app and Moodle LMS website. In order to support [https://fontawesome.com/v5/search?m=free font awesome icons]. We&#039;ve added a directive that uses prefixes on the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; attribute to use different font icons.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fas-&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;fa-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=solid Font awesome solid] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;far-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=regular Font awesome regular] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fab-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=brands Font awesome brands] library (But only a few are supported and we discourage to use them).&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;moodle-&amp;lt;/code&amp;gt; will use some svg icons [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/moodle imported from Moodle LMS].&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fam-&amp;lt;/code&amp;gt; will use [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/font-awesome customized font awesome icons].&lt;br /&gt;
* If the prefix is not found or not valid, the app will search the icon name on the [https://ionic.io/ionicons ionicons library].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example of usage to show icon &amp;quot;pizza-slice&amp;quot; from font-awesome regular library:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-icon name=&amp;quot;fas-pizza-slice&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We encourage the use of font-awesome icons to match the appearance from the LMS website version.&lt;br /&gt;
&lt;br /&gt;
===Specific component and directives for plugins===&lt;br /&gt;
These are component and directives created specifically for supporting Moodle plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to display a new content when clicked. This new content can be displayed in a new page or in the current page (only if the current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The params to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in v3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in same page or open a new one. &lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that doing &amp;lt;code&amp;gt;[useOtherData]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to go to a new content page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content &lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; &lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to load new content in current page using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot; samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked. The action to do when the WS call is successful depends on the provided data: display a message, go back or refresh current view.&lt;br /&gt;
&lt;br /&gt;
If you want to load a new content when the WS call is done, please see [[#core-site-plugins-call-ws-new-content|&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The params for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;successMessage&amp;lt;/code&amp;gt; (string) — Message to show on success. If not supplied, no message. If supplied but empty, defaults to &amp;quot;Success&amp;quot;.&lt;br /&gt;
* &amp;lt;code&amp;gt;goBackOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to go back if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to refresh the current view if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to send some data to the server without using cache, displaying default messages and refreshing on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage successMessage&lt;br /&gt;
        refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to send some data to the server using cache without confirming, going back on success and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        goBackOnSuccess=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Same as the previous example, but implementing custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;certificateViewed($event)&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.certificateViewed = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked and load a new content passing the WS result as arguments. This new content can be displayed in a new page or in the same page (only if current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to load some new content when done, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The parameters to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in the same page or open a new one.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. The format is the same as in &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;jsData&amp;lt;/code&amp;gt; (any) — JS variables to pass to the new page so they can be used in the template or JS. If &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; is supplied instead of an object, all initial variables from current page will be copied. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;newContentPreSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in 3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to get some data from the server without using cache, showing default confirm and displaying a new page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage&lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to get some data from the server using cache, without confirm, displaying new content in same page and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Same as the previous example, but implementing a custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS as soon as the template is loaded. This directive is meant for actions to do in the background, like calling logging Web Services.&lt;br /&gt;
&lt;br /&gt;
If you want to call a WS when the user clicks on a certain element, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span core-site-plugins-call-ws-on-load&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Advanced features==&lt;br /&gt;
===Display the plugin only if certain conditions are met===&lt;br /&gt;
You might want to display your plugin in the mobile app only if certain dynamic conditions are met, so the plugin would be displayed only for some users. This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section ahead).&lt;br /&gt;
&lt;br /&gt;
All initialisation methods are called as soon as your plugin is retrieved. If you don&#039;t want your plugin to be displayed for the current user, then you should return the following in the initialisation method (only for Moodle site 3.8 and onwards):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;disabled&#039; =&amp;gt; true];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If the Moodle site is older than 3.8, then the initialisation method should return this instead:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;javascript&#039; =&amp;gt; &#039;this.HANDLER_DISABLED&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
On the other hand, you might want to display a plugin only for certain courses (&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;) or only if the user is viewing certain users&#039; profiles (&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;). This can be achieved with the initialisation method too.&lt;br /&gt;
&lt;br /&gt;
In the initialisation method you can return a &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; property with two fields in it: &amp;lt;code&amp;gt;courses&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;users&amp;lt;/code&amp;gt;. If you return a list of courses IDs in this property, then your plugin will only be displayed when the user views any of those courses. In the same way, if you return a list of user IDs then your plugin will only be displayed when the user views any of those users&#039; profiles.&lt;br /&gt;
===Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;===&lt;br /&gt;
The values returned by the functions in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; are added to a variable so they can be used both in JavaScript and in templates. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call is added to a variable named &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt;, while the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call is added to a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call will be passed to the JS and template of all the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; calls in that handler. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call will only be passed to the JS and template returned by that &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call.&lt;br /&gt;
&lt;br /&gt;
This means that, in your JavaScript, you can access and use the data like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.CONTENT_OTHERDATA.myVar;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the template you could use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
{{ CONTENT_OTHERDATA.myVar }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; is the name we put to one of our variables, it can be any name that you want. In the example above, this is the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
[&#039;myVar&#039; =&amp;gt; &#039;Initial value&#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Example====&lt;br /&gt;
In our plugin, we want to display an input text with a certain initial value. When the user clicks a button, we want the value in the input to be sent to a certain Web Service. This can be done using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We will return the initial value of the input in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; of our PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;myVar&#039; =&amp;gt; &#039;My initial value&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then in the template we will use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item text-wrap&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label position=&amp;quot;stacked&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.textlabel | translate }}&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;text&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.myVar&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws name=&amp;quot;mod_certificate_my_webservice&amp;quot; [useOtherDataForWS]=&amp;quot;[&#039;myVar&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we are creating an input text and we use &amp;lt;code&amp;gt;[(ngModel)]&amp;lt;/code&amp;gt; to use the value in &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; as the initial value and to store the changes in the same &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This means that the initial value of the input will be &amp;quot;My initial value&amp;quot;, and if the user changes the value of the input these changes will be applied to the &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This is called 2-way data binding in Angular.&lt;br /&gt;
&lt;br /&gt;
Then we add a button to send this data to a WS, and for that we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive. We use the &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; attribute to specify which variable from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; we want to send to our WebService. So if the user enters &amp;quot;A new value&amp;quot; in the input and then clicks the button, it will call the WebService &amp;lt;code&amp;gt;mod_certificate_my_webservice&amp;lt;/code&amp;gt; and will send as a parameter &amp;lt;code&amp;gt;[&#039;myVar&#039; =&amp;gt; &#039;A new value&#039;]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We can also achieve the same result using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; attribute of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive instead of using &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws &lt;br /&gt;
        name=&amp;quot;mod_certificate_my_webservice&amp;quot; [params]=&amp;quot;{myVar: CONTENT_OTHERDATA.myVar}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The Web Service call will be exactly the same with both versions.&lt;br /&gt;
&lt;br /&gt;
Notice that this example could be done without using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; too, using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; input of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive.&lt;br /&gt;
===Running JS code after a content template has loaded===&lt;br /&gt;
When you return JavaScript code from a handler function using the &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; array key, this code is executed immediately after the web service call returns, which may be before the returned template has been rendered into the DOM. &lt;br /&gt;
&lt;br /&gt;
If your code needs to run after the DOM has been updated, you can use &amp;lt;code&amp;gt;setTimeout&amp;lt;/code&amp;gt; to call it. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&lt;br /&gt;
    &#039;template&#039; =&amp;gt; [&lt;br /&gt;
        // ...&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;javascript&#039; =&amp;gt; &#039;setTimeout(function() { console.log(&amp;quot;DOM is available now&amp;quot;); });&#039;,&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    &#039;files&#039; =&amp;gt; [],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice that if you wanted to write a lot of code here, you might be better off putting it in a function defined in the response from an initialisation template, so that it does not get loaded again with each page of content.&lt;br /&gt;
===JS functions visible in the templates===&lt;br /&gt;
The app provides some JavaScript functions that can be used from the templates to update, refresh or view content. These are the functions:&lt;br /&gt;
* &amp;lt;code&amp;gt;openContent(title: string, args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Open a new page to display some new content. You need to specify the &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; of the new page and the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshContent(showSpinner = true)&amp;lt;/code&amp;gt; — Refresh the current content. By default, it will display a spinner while refreshing. If you don&#039;t want it to be displayed, you should pass &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; as a parameter.&lt;br /&gt;
* &amp;lt;code&amp;gt;updateContent(args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Refresh the current content using different parameters. You need to specify the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Group selector=====&lt;br /&gt;
Imagine we have an activity that uses groups and we want to let the user select which group they want to see. A possible solution would be to return all the groups in the same template (hidden), and then show the group user selects. However, we can make it more dynamic and return only the group the user is requesting.&lt;br /&gt;
&lt;br /&gt;
To do so, we&#039;ll use a drop down to select the group. When the user selects a group using this drop down, we&#039;ll update the page content to display the new group.&lt;br /&gt;
&lt;br /&gt;
The main difficulty in this is to tell the view which group needs to be selected when the view is loaded. There are 2 ways to do it: using plain HTML or using Angular&#039;s &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;.&lt;br /&gt;
======Using plain HTML======&lt;br /&gt;
We need to add a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; attribute to the option that needs to be selected. To do so, we need to pre-caclulate the selected option in the PHP code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// Detect which group is selected.&lt;br /&gt;
foreach ($groups as $gid=&amp;gt;$group) {&lt;br /&gt;
    $group-&amp;gt;selected = $gid === $groupid;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$data = [&lt;br /&gt;
    &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
    &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
    &#039;groups&#039; =&amp;gt; $groups,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the code above, we&#039;re retrieving the groups the user can see and then we&#039;re adding a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; boolean to each one to determine which one needs to be selected in the drop down. Finally, we pass the list of groups to the template.&lt;br /&gt;
&lt;br /&gt;
In the template, we display the drop down like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: $event})&amp;quot; interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot; &amp;lt;%#selected%&amp;gt;selected&amp;lt;%/selected%&amp;gt; &amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The &amp;lt;code&amp;gt;ionChange&amp;lt;/code&amp;gt; function will be called every time the user selects a different group with the drop down. We&#039;re using the &amp;lt;code&amp;gt;updateContent&amp;lt;/code&amp;gt; function to update the current view using the new group. &amp;lt;code&amp;gt;$event&amp;lt;/code&amp;gt; is an Angular variable that will have the selected value (in our case, the group ID that was just selected). This is enough to make the group selector work.&lt;br /&gt;
======Using &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;======&lt;br /&gt;
&amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; is an Angular directive that allows storing the value of a certain input or select in a JavaScript variable, and also the opposite way: tell the input or select which value to set. The main problem is that we cannot initialise a JavaScript variable from the template, so we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the PHP function we&#039;ll return the group that needs to be selected in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
        &#039;group&#039; =&amp;gt; $groupid,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we don&#039;t need to iterate over the groups array like in the plain HTML example. However, now we&#039;re returning the group id in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array. As it&#039;s explained in the [[#Using_otherdata|Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;]] section, this &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; is visible in the templates inside a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. So in the template we&#039;ll use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.group&amp;quot;&lt;br /&gt;
        (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: CONTENT_OTHERDATA.group})&amp;quot;&lt;br /&gt;
        interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Use the rich text editor===&lt;br /&gt;
The rich text editor included in the app requires a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt; to work. You can use the &amp;lt;code&amp;gt;FormBuilder&amp;lt;/code&amp;gt; library to create this control (or to create a whole &amp;lt;cide&amp;gt;FormGroup if you prefer).&lt;br /&gt;
&lt;br /&gt;
With the following JavaScript you&#039;ll be able to create a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.control = this.FormBuilder.control(this.CONTENT_OTHERDATA.rte);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we&#039;re using a value returned in &amp;lt;code&amp;gt;OTHERDATA&amp;lt;/code&amp;gt; as the initial value of the rich text editor, but you can use whatever you want.&lt;br /&gt;
&lt;br /&gt;
Then you need to pass this control to the rich text editor in your template:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;core-rich-text-editor item-content [control]=&amp;quot;control&amp;quot; placeholder=&amp;quot;Enter your text here&amp;quot; name=&amp;quot;rte_answer&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-rich-text-editor&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, there are several ways to send the value in the rich text editor to a Web Service to save it. This is one of the simplest options:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_webservice&amp;quot; [params]=&amp;quot;{rte: control.value}&amp;quot; ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re passing the value of the rich text editor as a parameter to our Web Service.&lt;br /&gt;
===Initialisation===&lt;br /&gt;
All handlers can specify an &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; method in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. This method is meant to return some JavaScript code that needs to be executed as soon as the plugin is retrieved.&lt;br /&gt;
&lt;br /&gt;
When the app retrieves all the handlers, the first thing it will do is call the &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt; Web Service with the initialisation method. This WS call will only receive the default arguments.&lt;br /&gt;
&lt;br /&gt;
The app will immediately execute the JavaScript code returned by this WS call. This JavaScript can be used to manually register your handlers in the delegates you want, without having to rely on the default handlers built based on the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; data.&lt;br /&gt;
&lt;br /&gt;
The templates returned by this method will be added to a &amp;lt;code&amp;gt;INIT_TEMPLATES&amp;lt;/code&amp;gt; variable that will be passed to all the JavaScript code of that handler. This means that the JavaScript returned by the initialisation method or the main method can access any of the templates HTML like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.INIT_TEMPLATES[&#039;main&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In this case, &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is the ID of the template we want to use.&lt;br /&gt;
&lt;br /&gt;
The same happens with the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the initialisation method, it is added to an &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt; variable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; field returned by this call will be used to determine if your handler is enabled or not. For example, if your handler is for the delegate &amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt; and you return a list of course ids in &amp;lt;code&amp;gt;restrict.courses&amp;lt;/code&amp;gt;, then your handler will only be enabled in the courses you returned. This only applies to the default handlers, if you register your own handler using the JavaScript code then you should check yourself if the handler is enabled.&lt;br /&gt;
&lt;br /&gt;
Finally, if you return an object in this initialisation JavaScript code, all the properties of that object will be passed to all the JavaScript code of that handler so you can use them when the code is run. For example, if your JavaScript code does something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    MyAddonClass: new MyAddonClass()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then, for the rest of JavaScript code of your handler (for example, the main method) you can use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.MyAddonClass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Link handlers=====&lt;br /&gt;
A link handler allows you to decide what to do when a link with a certain URL is clicked. This is useful, for example, to open your plugin page when a link to your plugin is clicked.&lt;br /&gt;
&lt;br /&gt;
After the 4.0 version, the Moodle app automatically creates two link handlers for module plugins, you don&#039;t need to create them in your plugin&#039;s Javascript code anymore:&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginanme/view.php?id=X&#039;&#039;. When this link is clicked, it will open your module in the app.&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginname/index.php?id=X&#039;&#039;. When this link is clicked, it will open a page in the app listing all the modules of your type inside a certain course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Link handlers have some advanced features that allow you to change how links behave under different conditions.&lt;br /&gt;
======Patterns======&lt;br /&gt;
You can define a Regular Expression pattern to match certain links. This will apply the handler only to links that match the pattern.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = RegExp(&#039;\/mod\/foo\/specialpage.php&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Priority======&lt;br /&gt;
Multiple link handlers may apply to a given link. You can define the order of precedence by setting the priority; the handler with the highest priority will be used.&lt;br /&gt;
&lt;br /&gt;
All default handlers have a priority of 0, so 1 or higher will override the default.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Multiple actions======&lt;br /&gt;
Once a link has been matched, the handler&#039;s &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method determines what the link should do. This method has access to the URL and its parameters.&lt;br /&gt;
&lt;br /&gt;
Different actions can be returned depending on different conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                action: function(siteId, navCtrl) {&lt;br /&gt;
                    // The actual behaviour of the link goes here.&lt;br /&gt;
                },&lt;br /&gt;
                sites: [&lt;br /&gt;
                    // ...&lt;br /&gt;
                ],&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                // ...&lt;br /&gt;
            },&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once handlers have been matched for a link, the actions will be fetched for all the matching handlers, in priorty order. The first valid action will be used to open the link.&lt;br /&gt;
&lt;br /&gt;
If your handler is matched with a link, but a condition assessed in the &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method means you want to revert to the next highest priorty handler, you can invalidate your action by settings its sites propety to an empty array.&lt;br /&gt;
======Complex example======&lt;br /&gt;
This will match all URLs containing &amp;lt;code&amp;gt;/mod/foo/&amp;lt;/code&amp;gt;, and force those with an id parameter that&#039;s not in the &amp;lt;code&amp;gt;supportedModFoos&amp;lt;/code&amp;gt; array to open in the user&#039;s browser, rather than the app.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const that = this;&lt;br /&gt;
const supportedModFoos = [...];&lt;br /&gt;
&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = new RegExp(&#039;\/mod\/foo\/&#039;);&lt;br /&gt;
        this.name = &#039;AddonModFooLinkHandler&#039;;&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {     &lt;br /&gt;
        const action = {&lt;br /&gt;
            action() {&lt;br /&gt;
                that.CoreUtilsProvider.openInBrowser(url);&lt;br /&gt;
            },&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (supportedModFoos.indexOf(parseInt(params.id)) !== -1) {&lt;br /&gt;
            action.sites = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return [action];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreContentLinksDelegate.registerHandler(new AddonModFooLinkHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Module prefetch handler=====&lt;br /&gt;
The &amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt; handler allows you to define a list of offline functions to prefetch a module. However, you might want to create your own prefetch handler to determine what needs to be downloaded. For example, you might need to chain WS calls (pass the result of a WS call to the next one), and this cannot be done using offline functions.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to create a prefetch handler using the initialisation JS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Create a class that extends from CoreCourseActivityPrefetchHandlerBase.&lt;br /&gt;
class AddonModCertificateModulePrefetchHandler extends CoreCourseActivityPrefetchHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.name = &#039;AddonModCertificateModulePrefetchHandler&#039;;&lt;br /&gt;
        this.modName = &#039;certificate&#039;;&lt;br /&gt;
&lt;br /&gt;
        // This must match the plugin identifier from db/mobile.php,&lt;br /&gt;
        // otherwise the download link in the context menu will not update correctly.&lt;br /&gt;
        this.component = &#039;mod_certificate&#039;;&lt;br /&gt;
        this.updatesNames = /^configuration$|^.*files$/;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Override the prefetch call.&lt;br /&gt;
    prefetch(module, courseId, single, dirPath) {&lt;br /&gt;
        return this.prefetchPackage(module, courseId, single, prefetchCertificate);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prefetchCertificate(module, courseId, single, siteId) {&lt;br /&gt;
    // Perform all the WS calls.&lt;br /&gt;
    // You can access most of the app providers using that.ClassName. E.g. that.CoreWSProvider.call().&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseModulePrefetchDelegate.registerHandler(new AddonModCertificateModulePrefetchHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
One relatively simple full example is where you have a function that needs to work offline, but it has an additional argument other than the standard ones. You can imagine for this an activity like the book module, where it has multiple pages for the same &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;. The app will not automatically work with this situation — it will call the offline function with the standard arguments only — so you won&#039;t be able to prefetch all the possible parameters.&lt;br /&gt;
&lt;br /&gt;
To deal with this, you need to implement a web service in your Moodle component that returns the list of possible extra arguments, and then you can call this web service and loop around doing the same thing the app does when it prefetches the offline functions. Here is an example from a third-party module (showing only the actual prefetch function, the rest of the code is as above) where there are multiple values of a custom &amp;lt;code&amp;gt;section&amp;lt;/code&amp;gt; parameter for the mobile function &amp;lt;code&amp;gt;mobile_document_view&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function prefetchOucontent(module, courseId, single, siteId) {&lt;br /&gt;
    var component = &#039;mod_oucontent&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Get the site, first.&lt;br /&gt;
    return that.CoreSitesProvider.getSite(siteId).then(function(site) {&lt;br /&gt;
        // Read the list of pages in this document using a web service.&lt;br /&gt;
        return site.read(&#039;mod_oucontent_get_page_list&#039;, {&#039;cmid&#039;: module.id}).then(function(response) {&lt;br /&gt;
            var promises = [];&lt;br /&gt;
&lt;br /&gt;
            // For each page, read and process the page - this is a copy of logic in the app at&lt;br /&gt;
            // siteplugins.ts (prefetchFunctions), but modified to add the custom argument.&lt;br /&gt;
            for(var i = 0; i &amp;lt; response.length; i++) {&lt;br /&gt;
                var args = {&lt;br /&gt;
                    courseid: courseId,&lt;br /&gt;
                    cmid: module.id,&lt;br /&gt;
                    userid: site.getUserId()&lt;br /&gt;
                };&lt;br /&gt;
                if (response[i] !== &#039;&#039;) {&lt;br /&gt;
                    args.section = response[i];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                promises.push(that.CoreSitePluginsProvider.getContent(&lt;br /&gt;
                        component, &#039;mobile_document_view&#039;, args).then(&lt;br /&gt;
                        function(result) {&lt;br /&gt;
                            var subPromises = [];&lt;br /&gt;
                            if (result.files &amp;amp;&amp;amp; result.files.length) {&lt;br /&gt;
                                subPromises.push(that.CoreFilepoolProvider.downloadOrPrefetchFiles(&lt;br /&gt;
                                        site.id, result.files, true, false, component, module.id));&lt;br /&gt;
                            }&lt;br /&gt;
                            return Promise.all(subPromises);&lt;br /&gt;
                        }));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(promises);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Single activity course format=====&lt;br /&gt;
In the following example, the value of &amp;lt;code&amp;gt;INIT_TEMPLATES[&#039;main&#039;]&amp;lt;/code&amp;gt; is:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;componentClass&amp;quot; [data]=&amp;quot;data&amp;quot;&amp;gt;&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This template is returned by the initialisation method. And this is the JavaScript code returned:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatComponent {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.data = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ngOnChanges(changes) {&lt;br /&gt;
        var self = this;&lt;br /&gt;
&lt;br /&gt;
        if (this.course &amp;amp;&amp;amp; this.sections &amp;amp;&amp;amp; this.sections.length) {&lt;br /&gt;
            var module = this.sections[0] &amp;amp;&amp;amp; this.sections[0].modules &amp;amp;&amp;amp; this.sections[0].modules[0];&lt;br /&gt;
            if (module &amp;amp;&amp;amp; !this.componentClass) {&lt;br /&gt;
                that.CoreCourseModuleDelegate.getMainComponent(that.Injector, this.course, module).then((component) =&amp;gt; {&lt;br /&gt;
                    self.componentClass = component || that.CoreCourseUnsupportedModuleComponent;&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.data.courseId = this.course.id;&lt;br /&gt;
            this.data.module = module;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    doRefresh(refresher, done) {&lt;br /&gt;
        return Promise.resolve(this.dynamicComponent.callComponentFunction(&amp;quot;doRefresh&amp;quot;, [refresher, done]));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatHandler {&lt;br /&gt;
    &lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.name = &#039;singleactivity&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    isEnabled() {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canViewAllSections() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseTitle(course, sections) {&lt;br /&gt;
        if (sections &amp;amp;&amp;amp; sections[0] &amp;amp;&amp;amp; sections[0].modules &amp;amp;&amp;amp; sections[0].modules[0]) {&lt;br /&gt;
            return sections[0].modules[0].name;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return course.fullname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displayEnableDownload() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displaySectionSelector() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseFormatComponent() {&lt;br /&gt;
        return that.CoreCompileProvider.instantiateDynamicComponent(that.INIT_TEMPLATES[&#039;main&#039;], AddonSingleActivityFormatComponent);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseFormatDelegate.registerHandler(new AddonSingleActivityFormatHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using the JavaScript API===&lt;br /&gt;
The JavaScript API is only supported by the delegates specified in the [[#Templates_downloaded_on_login_and_rendered_using_JS_data_2|Templates downloaded on login and rendered using JS data]] section. This API allows you to override any of the functions of the default handler.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; specified in a handler registered in the &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; will be called immediately after the initialisation method, and the JavaScript returned by this method will be run. If this JavaScript code returns an object with certain functions, these functions will override the ones in the default handler.&lt;br /&gt;
&lt;br /&gt;
For example, if the JavaScript returned by the method returns something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The the &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function of the default handler will be overridden by the returned &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
The default handler for &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; only has 2 functions: &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt;. In addition, the JavaScript code can return an extra function named &amp;lt;code&amp;gt;componentInit&amp;lt;/code&amp;gt; that will be executed when the component returned by &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; is initialised.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to support the text user profile field using this API:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
var result = {&lt;br /&gt;
    componentInit: function() {&lt;br /&gt;
        if (this.field &amp;amp;&amp;amp; this.edit &amp;amp;&amp;amp; this.form) {&lt;br /&gt;
            this.field.modelName = &#039;profile_field_&#039; + this.field.shortname;&lt;br /&gt;
&lt;br /&gt;
            if (this.field.param2) {&lt;br /&gt;
                this.field.maxlength = parseInt(this.field.param2, 10) || &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.field.inputType = that.CoreUtilsProvider.isTrueOrOne(this.field.param3) ? &#039;password&#039; : &#039;text&#039;;&lt;br /&gt;
&lt;br /&gt;
            var formData = {&lt;br /&gt;
                value: this.field.defaultdata,&lt;br /&gt;
                disabled: this.disabled,&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            this.form.addControl(this.field.modelName,&lt;br /&gt;
                that.FormBuilder.control(formData, this.field.required &amp;amp;&amp;amp; !this.field.locked ? that.Validators.required : null));&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        var name = &#039;profile_field_&#039; + field.shortname;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            type: &amp;quot;text&amp;quot;,&lt;br /&gt;
            name: name,&lt;br /&gt;
            value: that.CoreTextUtilsProvider.cleanTags(formValues[name]),&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Translate dynamic strings===&lt;br /&gt;
If you wish to have an element that displays a localised string based on value from your template you can doing something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_myactivity.&amp;lt;% status %&amp;gt;&#039; | translate }}&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This could save you from having to write something like when only one value should be displayed:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        &amp;lt;%#isedting%&amp;gt;{{ &#039;plugin.mod_myactivity.editing&#039; | translate }}&amp;lt;%/isediting%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isopen%&amp;gt;{{ &#039;plugin.mod_myactivity.open&#039; | translate }}&amp;lt;%/isopen%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isclosed%&amp;gt;{{ &#039;plugin.mod_myactivity.closed&#039; | translate }}&amp;lt;%/isclosed%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using strings with dates===&lt;br /&gt;
If you have a string that you wish to pass a formatted date, for example in the Moodle language file you have:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;strwithdate&#039;] = &#039;This string includes a date of {$a-&amp;gt;date} in the middle of it.&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can localise the string correctly in your template using something like the following:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{ &#039;plugin.mod_myactivity.strwithdate&#039; | translate: {$a: { date: &amp;lt;% timestamp %&amp;gt; * 1000 | coreFormatDate: &amp;quot;dffulldate&amp;quot; } } }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A Unix timestamp must be multiplied by 1000 as the Mobile App expects millisecond timestamps, whereas Unix timestamps are in seconds.&lt;br /&gt;
===Support push notification clicks===&lt;br /&gt;
If your plugin sends push notifications to the app, you might want to open a certain page in the app when the notification is clicked. There are several ways to achieve this.&lt;br /&gt;
&lt;br /&gt;
The easiest way is to include a &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; in your notification. When the notification is clicked, the app will try to open the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; will also be displayed in web. If you want to use a specific URL for the app, different than the one displayed in web, you can do so by returning a &amp;lt;code&amp;gt;customdata&amp;lt;/code&amp;gt; array that contains an &amp;lt;code&amp;gt;appurl&amp;lt;/code&amp;gt; property:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$notification-&amp;gt;customdata = [&lt;br /&gt;
    &#039;appurl&#039; =&amp;gt; $myurl-&amp;gt;out(),&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In both cases you will have to create a link handler to treat the URL. For more info on how to create the link handler, please see [[#Advanced_link_handler|how to create an advanced link handler]].&lt;br /&gt;
&lt;br /&gt;
If you want to do something that only happens when the notification is clicked, not when the link is clicked, you&#039;ll have to implement a push click handler yourself. The way to create it is similar to [[#Advanced_link_handler|creating an advanced link handler]], but you&#039;ll have to use &amp;lt;code&amp;gt;CorePushNotificationsDelegate&amp;lt;/code&amp;gt; and your handler will have to implement the properties and functions defined in the [https://github.com/moodlehq/moodleapp/blob/master/src/core/features/pushnotifications/services/push-delegate.ts#L27 CorePushNotificationsClickHandler] interface.&lt;br /&gt;
===Implement a module similar to mod_label===&lt;br /&gt;
In Moodle 3.8 or higher, if your plugin doesn&#039;t support &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt; and you don&#039;t specify a &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; then the module will only display the module description in the course page and it won&#039;t be clickable in the app, just like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt;. You can decide if you want the module icon to be displayed or not (if you don&#039;t want it to be displayed, then don&#039;t define it in &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
However, if your plugin needs to work in previous versions of Moodle or you want to display something different than the description then you need a different approach.&lt;br /&gt;
&lt;br /&gt;
If your plugin wants to render something in the course page instead of just the module name and description you should specify the &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. The template returned by this method will be rendered in the course page. Please notice the HTML returned should not contain directives or components, only plain HTML.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want your module to be clickable then you just need to remove &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. With these 2 changes you can have a module that behaves like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt; in the app.&lt;br /&gt;
===Use Ionic navigation lifecycle functions===&lt;br /&gt;
Ionic let pages define some functions that will be called when certain navigation lifecycle events happen. For more info about these functions, see [https://ionicframework.com/docs/api/router-outlet Ionic&#039;s documentation].&lt;br /&gt;
&lt;br /&gt;
You can define these functions in your plugin javascript:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.ionViewWillLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In addition to that, you can also implement &amp;lt;code&amp;gt;canLeave&amp;lt;/code&amp;gt; to use Angular route guards:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.canLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So for example you can make your plugin ask for confirmation if the user tries to leave the page when he has some unsaved data.&lt;br /&gt;
===Module plugins: dynamically determine if a feature is supported===&lt;br /&gt;
In Moodle you can specify if your plugin supports a certain feature, like &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. If your plugin will always support or not a certain feature, then you can use the &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; to specify it ([[#Options_only_for_CoreCourseModuleDelegate|see more documentation about this]]). But if you need to calculate it dynamically then you will have to create a function to calculate it. &lt;br /&gt;
&lt;br /&gt;
This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section above). The JavaScript returned by your initialisation method will need to define a function named &amp;lt;code&amp;gt;supportsFeature&amp;lt;/code&amp;gt; that will receive the name of the feature:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    supportsFeature: function(featureName) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;.&lt;br /&gt;
== Testing ==&lt;br /&gt;
You can also write automated tests for your plugin using Behat, you can read more about it on the [[Acceptance testing for the Moodle App]] page.&lt;br /&gt;
== Upgrading plugins from an older version ==&lt;br /&gt;
If you added mobile support to your plugin for the Ionic 3 version of the app (previous to the 3.9.5 release), you will probably need to make some changes to make it compatible with Ionic 5.&lt;br /&gt;
&lt;br /&gt;
Learn more at the [[Moodle App Plugins Upgrade Guide]].&lt;br /&gt;
==Troubleshooting==&lt;br /&gt;
=== Invalid response received ===&lt;br /&gt;
You might receive this error when using the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive or similar. By default, the app expects all Web Service calls to return an object, if your Web Service returns another type (string, boolean, etc.) then you need to specify it using the &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; attribute of the directive. For example, if your WS returns a boolean value, then you should specify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{typeExpected: &#039;boolean&#039;}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In a similar way, if your Web Service returns &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; you need to tell the app not to expect any result using &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{responseExpected: false}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS ===&lt;br /&gt;
Some directives allow you to specify a form id or name to send the data from the form to a certain WS. These directives look for HTML inputs to retrieve the data to send. However, &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; don&#039;t use HTML inputs, they simulate them, so the directive isn&#039;t going to find their data and so it won&#039;t be sent to the Web Service.&lt;br /&gt;
&lt;br /&gt;
There are 2 workarounds to fix this problem.&lt;br /&gt;
==== Sending the data manually ====&lt;br /&gt;
The first solution is to send the missing params manually using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; property. We will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input value in a variable, and this variable will be passed to the parameters. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a template like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group name=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, responses: responses}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Basically, you need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to the affected element (in this case, the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt;). You can put whatever name you want as the value, we used &amp;quot;responses&amp;quot;. With this, every time the user selects a radio button the value will be stored in a variable called &amp;quot;responses&amp;quot;. Then, in the button we are passing this variable to the parameters of the Web Service.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute has priority over &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;, so if you have an input with &amp;lt;code&amp;gt;name=&amp;quot;responses&amp;quot;&amp;lt;/code&amp;gt; it will override what you&#039;re manually passing to &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;.&lt;br /&gt;
==== Using a hidden input ====&lt;br /&gt;
Since the directive is looking for HTML inputs, you need to add one with the value to send to the server. You can use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to synchronise your radio/checkbox/select with the new hidden input. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a radio button like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot; [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;hidden&amp;quot; [ngModel]=&amp;quot;responses&amp;quot; name=&amp;quot;responses&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we&#039;re using a variable called &amp;quot;responses&amp;quot; to synchronise the data between the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt; and the hidden input. You can use whatever name you want.&lt;br /&gt;
=== I can&#039;t return an object or array in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; ===&lt;br /&gt;
If you try to return an object or an array in any field inside &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, the Web Service call will fail with the following error:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Scalar type expected, array or object received&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Each field in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; must be a string, number or boolean; it cannot be an object or array. To make it work, you need to encode your object or array into a JSON string:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($data)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app will automatically parse this JSON and convert it back into an array or object.&lt;br /&gt;
==Examples==&lt;br /&gt;
===Accepting dynamic names in a Web Service===&lt;br /&gt;
We want to display a form where the names of the fields are dynamic, like it happens in quiz. This data will be sent to a new Web Service that we have created.&lt;br /&gt;
&lt;br /&gt;
The first issue we find is that the Web Service needs to define the names of the parameters received, but in this case they&#039;re dynamic. The solution is to accept an array of objects with name and value. So in the &amp;lt;code&amp;gt;_parameters()&amp;lt;/code&amp;gt; function of our new Web Service, we will add this parameter:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;data&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
     new external_single_structure(&lt;br /&gt;
        [&lt;br /&gt;
            &#039;name&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data name&#039;),&lt;br /&gt;
            &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data value&#039;),&lt;br /&gt;
        ]&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;The data to be saved&#039;, VALUE_DEFAULT, []&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now we need to adapt our form to send the data as the Web Service requires it. In our template, we have a button with the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive that will send the form data to our Web Service. To make this work we will have to pass the parameters manually, without using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute, because we need to format the data before it is sent.&lt;br /&gt;
&lt;br /&gt;
Since we will send the parameters manually and we want it all to be sent in the same array, we will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input data into a variable that we&#039;ll call &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;, but you can use the name you want. This variable will be an object that will hold the input data with the format &amp;quot;name-&amp;gt;value&amp;quot;. For example, if I have an input with name &amp;quot;a1&amp;quot; and value &amp;quot;My answer&amp;quot;, the data object will be:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{a1: &#039;My answer&#039;}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So we need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to all the inputs whose values need to be sent to the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; WS param. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-input name=&amp;quot;&amp;lt;% name %&amp;gt;&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.data[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re using &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; to store the data. We do it like this because we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the form, setting the values the user has already stored. If you don&#039;t need to initialise the form, then you can use the &amp;lt;code&amp;gt;dataObject&amp;lt;/code&amp;gt; variable, an empty object that the mobile app creates for you:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[(ngModel)]=&amp;quot;dataObject[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app has a function that allows you to convert this data object into an array like the one the WS expects: &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt;. So in our button we&#039;ll use this function to format the data before it&#039;s sent:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_ws_name&amp;quot;&lt;br /&gt;
    [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, data: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.data, &#039;name&#039;, &#039;value&#039;)}&amp;quot;&lt;br /&gt;
    successMessage&lt;br /&gt;
    refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see in the example above, we&#039;re specifying that the keys of the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object need to be stored in a property called &amp;quot;name&amp;quot;, and the values need to be stored in a property called &amp;quot;value&amp;quot;. If your Web Service expects different names you need to change the parameters of the &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
If you open your plugin now in the app it will display an error in the JavaScript console. The reason is that the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; variable doesn&#039;t exist inside &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. As it is explained in previous sections, &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; holds the data that you return in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; for your method. We&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the values to be displayed in the form.&lt;br /&gt;
&lt;br /&gt;
If the user hasn&#039;t answered the form yet, we can initialise the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object as an empty object. Please remember that we cannot return arrays or objects in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, so we&#039;ll return a JSON string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; &#039;{}&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
With the code above, the form will always be empty when the user opens it. But now we want to check if the user has already answered the form and fill the form with the previous values. We will do it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$userdata = get_user_responses(); // It will held the data in a format name-&amp;gt;value. Example: [&#039;a1&#039; =&amp;gt; &#039;My value&#039;].&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($userdata)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now the user will be able to see previous values when the form is opened, and clicking the button will send the data to our Web Service in array format.&lt;br /&gt;
==Moodle plugins with mobile support==&lt;br /&gt;
* Group choice: [https://moodle.org/plugins/mod_choicegroup Moodle plugins directory entry] and [https://github.com/ndunand/moodle-mod_choicegroup code in github].&lt;br /&gt;
* Custom certificate: [https://moodle.org/plugins/mod_customcert Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_customcert code in github].&lt;br /&gt;
* Gapfill question type: [https://moodle.org/plugins/qtype_gapfill Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_gapfill in github].&lt;br /&gt;
* Wordselect question type: [https://moodle.org/plugins/qtype_wordselect Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_wordselect in github].&lt;br /&gt;
* RegExp question type: [https://moodle.org/plugins/qtype_regexp Moodle plugins directory entry] and [https://github.com/rezeau/moodle-qtype_regexp in github].&lt;br /&gt;
* Certificate: [https://moodle.org/plugins/mod_certificate Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_certificate in github].&lt;br /&gt;
* Attendance [https://moodle.org/plugins/mod_attendance Moodle plugins directory entry] and [https://github.com/danmarsden/moodle-mod_attendance in github].&lt;br /&gt;
* ForumNG (unfinished support) [https://moodle.org/plugins/mod_forumng Moodle plugins directory entry] and [https://github.com/moodleou/moodle-mod_forumng in github].&lt;br /&gt;
* News block [https://github.com/moodleou/moodle-block_news in github].&lt;br /&gt;
* H5P activity module [https://moodle.org/plugins/mod_hvp Moodle plugins directory entry] and [https://github.com/h5p/h5p-moodle-plugin in github].&lt;br /&gt;
See the complete list in [https://moodle.org/plugins/browse.php?list=award&amp;amp;id=6 the plugins database] (it may contain some outdated plugins).&lt;br /&gt;
=== Mobile app support award ===&lt;br /&gt;
If you want your plugin to be awarded in the plugins directory and marked as supporting the mobile app, please feel encouraged to contact us via email at [mailto:mobile@moodle.com mobile@moodle.com].&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to include a link to your plugin page and the location of its code repository.&lt;br /&gt;
&lt;br /&gt;
See [https://moodle.org/plugins/?q=award:mobile-app the list of awarded plugins] in the plugins directory.&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Moodle App Ionic 5]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62055</id>
		<title>Moodle App Plugins Development Guide</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62055"/>
		<updated>2022-04-25T08:16:34Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: /* Step 3. Creating the template for the main function */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle App (Ionic 5)}}&lt;br /&gt;
If you want to add mobile support to your Moodle plugin, you can achieve it by extending different areas of the app using &#039;&#039;just PHP server side code&#039;&#039; and providing templates written with [https://ionicframework.com/docs/components Ionic] and custom components.&lt;br /&gt;
&lt;br /&gt;
You will have to:&lt;br /&gt;
# Create a &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file in your plugin. In this file, you will be able to indicate which areas of the app you want to extend. For example, adding a new option in the main menu, implementing support for a new activity module, including a new option in the course menu, including a new option in the user profile, etc. All the areas supported are described further in this document.&lt;br /&gt;
# Create new functions in a reserved namespace that will return the content of the new options. The content should be returned rendered as html. This html should use Ionic components so that it looks native, but it can be generated using mustache templates.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let’s clarify some points:&lt;br /&gt;
* You don’t need to create new Web Service functions (although you will be able to use them for advanced features). You just need plain php functions that will be placed in a reserved namespace.&lt;br /&gt;
* Those functions will be exported via the Web Service function &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt;.&lt;br /&gt;
* As arguments of your functions you will always receive the &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, some relevant details of the app (like the app version or the current language in the app), and some specific data depending on the type of plugin (&amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
* The mobile app also implements a list of custom Ionic components and directives that provide dynamic behaviour; like indicating that you are linking a file that can be downloaded, allowing a transition to new pages into the app calling a specific function in the server, submitting form data to the server, etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Getting started==&lt;br /&gt;
If you only want to write a plugin, it is not necessary that you set up your environment to work with the Moodle App. In fact, you don&#039;t even need to compile it. You can just [[Using the Moodle App in a browser|use a Chromium-based browser]] to add mobile support to your plugins!&lt;br /&gt;
&lt;br /&gt;
You can use the app from one of the hosted versions on [https://master.apps.moodledemo.net master.apps.moodledemo.net] (the latest stable version) and [https://integration.apps.moodledemo.net integration.apps.moodledemo.net] (the latest development version). If you need any specific environment (hosted versions are deployed with a &#039;&#039;production&#039;&#039; environment), you can also use [[Moodle App Docker Images|Docker images]]. And if you need to test your plugin in a native device, you can always use [https://download.moodle.org/mobile Moodle HQ&#039;s application].&lt;br /&gt;
&lt;br /&gt;
This should suffice for developing plugins. However, if you are working on advanced functionality and you need to run the application from the source code, you can find more information in the [[Moodle App Development Guide]].&lt;br /&gt;
===Development workflow===&lt;br /&gt;
Before getting into the specifics of your plugin, we recommend that you start adding a simple &amp;quot;Hello World&amp;quot; button in the app to see that everything works properly.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s say your plugin is called &amp;lt;code&amp;gt;local_hello&amp;lt;/code&amp;gt;, you can start by adding the following files:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;local_hello&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [&lt;br /&gt;
            &#039;hello&#039; =&amp;gt; [&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreMainMenuDelegate&#039;,&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;view_hello&#039;,&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;title&#039; =&amp;gt; &#039;hello&#039;,&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; &#039;earth&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [&lt;br /&gt;
            [&#039;hello&#039;, &#039;local_hello&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace local_hello\output;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    public static function view_hello() {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; &#039;&amp;lt;h1 class=&amp;quot;text-center&amp;quot;&amp;gt;{{ &amp;quot;plugin.local_hello.hello&amp;quot; | translate }}&amp;lt;/h1&amp;gt;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;lang/en/local_hello.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;hello&#039;] = &#039;Hello World&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once you&#039;ve done that, try logging into your site in the app and you should see a new button in the main menu or more menu (depending on the device) saying &amp;quot;Hello World&amp;quot;. If you press this button, you should see a page saying &amp;quot;Hello World!&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Congratualtions, you have written your first Moodle plugin with moodle support!&lt;br /&gt;
&lt;br /&gt;
You can read the rest of this page to learn more about mobile plugins and start working on your plugin. Here&#039;s some things to keep in mind:&lt;br /&gt;
* If you change the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file, you will have to refresh the browser. And remember to [https://developer.chrome.com/docs/devtools/network/reference/#disable-cache disable the network cache].&lt;br /&gt;
* If you change an existing template or function, you won’t have to refresh the browser. In most cases, doing a PTR (Pull To Refresh) in the page that displays the template will suffice.&lt;br /&gt;
* If any of these doesn&#039;t show your changes, you may need to [https://docs.moodle.org/311/en/Developer_tools#Purge_all_caches purge all caches] to avoid problems with the auto-loading cache.&lt;br /&gt;
* Ultimately, if that doesn&#039;t work either, you may have to log out from the site and log in again. If any changes affect plugin installation, you may also need to increase the version in your plugin&#039;s &amp;lt;code&amp;gt;version.php&amp;lt;/code&amp;gt; file and upgrade it in the site.&lt;br /&gt;
==Types of plugins==&lt;br /&gt;
There are 3 types of plugins:&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
[[File:Templates_downloaded_when_requested.png|thumb]]&lt;br /&gt;
With this type of plugin, the template of your plugin will be generated and downloaded when the user opens the plugin in the app. This means that your function will receive some context parameters. For example, if you&#039;re developing a course module plugin you will receive the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; (course module ID). You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
[[File:Templates_downloaded_on_login.png|thumb]]&lt;br /&gt;
With this type of plugin, the template for your plugin will be downloaded when the user logs in into the app and will be stored in the device. This means that your function will not receive any context parameters, and you need to return a generic template that will be built with JS data like the ones in the Moodle App. When the user opens a page that includes your plugin, your template will receive the required JS data and your template will be rendered. You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
You can always implement the whole plugin yourself using JavaScript instead of using our API. In fact, this is required if you want to implement some features like capturing links in the Mobile app. You can see the list of delegates that only support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
==Step by step example==&lt;br /&gt;
In this example, we are going to update an existing plugin, the [https://github.com/mdjnelson/moodle-mod_certificate Certificate activity module], that previously used a [[Moodle Mobile 2 (Ionic 1) Remote add-ons|Remote add-on]] (a legacy approach to implement mobile plugins).&lt;br /&gt;
&lt;br /&gt;
This is a simple activity module that displays the certificate issued for the current user along with the list of the dates of previously issued certificates. It also stores in the course log that the user viewed a certificate. This module also works offline: when the user downloads the course or activity, the data is pre-fetched and can be viewed offline.&lt;br /&gt;
===Step 1. Update the &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file===&lt;br /&gt;
In this case, we are updating an existing file. For new plugins, you should create this new file.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;mod_certificate&#039; =&amp;gt; [ // Plugin identifier&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;coursecertificate&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/mod/certificate/pix/icon.gif&#039;,&lt;br /&gt;
                    &#039;class&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
       &lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseModuleDelegate&#039;, // Delegate (where to display the link to the plugin)&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;mobile_course_view&#039;, // Main function in \mod_certificate\output\mobile&lt;br /&gt;
                &#039;offlinefunctions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;mobile_course_view&#039; =&amp;gt; [],&lt;br /&gt;
                    &#039;mobile_issues_view&#039; =&amp;gt; [],&lt;br /&gt;
                ], // Function that needs to be downloaded for offline.&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [ // Language strings that are used in all the handlers.&lt;br /&gt;
            [&#039;pluginname&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;summaryofattempts&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;getcertificate&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;requiredtimenotmet&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;viewcertificateviews&#039;, &#039;certificate&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Plugin identifier:&lt;br /&gt;
: A unique name for the plugin, it can be anything (there’s no need to match the module name).&lt;br /&gt;
&lt;br /&gt;
;Handlers (Different places where the plugin will display content):&lt;br /&gt;
: A plugin can be displayed in different views in the app. Each view should have a unique name inside the plugin scope (alphanumeric).&lt;br /&gt;
&lt;br /&gt;
; Display data:&lt;br /&gt;
: This is only needed for certain types of plugins. Also, depending on the type of delegate it may require additional (or less fields). In this case, we are indicating the module icon.&lt;br /&gt;
&lt;br /&gt;
; Delegate&lt;br /&gt;
: Where to display the link to the plugin, see the [[#Delegates|Delegates]] section for all the possible options.&lt;br /&gt;
&lt;br /&gt;
; Method:&lt;br /&gt;
: This is the method in the Moodle &amp;lt;code&amp;gt;\{component-name}\output\mobile&amp;lt;/code&amp;gt; class to be executed the first time the user clicks in the new option displayed in the app.&lt;br /&gt;
&lt;br /&gt;
; Offlinefunctions&lt;br /&gt;
: This is the list of functions that need to be called and stored when the user downloads a course for offline usage. Please note that you can add functions here that are not even listed in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. &lt;br /&gt;
: In our example, downloading for offline access will mean that we&#039;ll execute the functions for getting the certificate and issued certificates passing as parameters the current &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; (and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; when we are using the mod or course delegate). If we have the result of those functions stored in the app, we&#039;ll be able to display the certificate information even if the user is offline.&lt;br /&gt;
: Offline functions will be mostly used to display information for final users, any further interaction with the view won’t be supported offline (for example, trying to send information when the user is offline).&lt;br /&gt;
: You can indicate here other Web Services functions, indicating the parameters that they might need from a defined subset (currently &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Prefetching the module will also download all the files returned by the methods in these offline functions (in the &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; array).&lt;br /&gt;
: Note that if your functions use additional custom parameters (for example, if you implement multiple pages within a module&#039;s view function by using a &amp;lt;code&amp;gt;page&amp;lt;/code&amp;gt; parameter in addition to the usual &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;) then the app will not know which additional parameters to supply. In this case, do not list the function in &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt;; instead, you will need to manually implement a [[#Module_prefetch_handler|module prefetch handler]].&lt;br /&gt;
&lt;br /&gt;
;Lang:&lt;br /&gt;
: The language pack string ids used in the plugin by all the handlers. Normally these will be strings from your own plugin, however, you can list any strings you need here, like &amp;lt;code&amp;gt;[&#039;cancel&#039;, &#039;moodle&#039;]&amp;lt;/code&amp;gt;. If you do this, be warned that in the app you will then need to refer to that string as &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.myplugin.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (not &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.moodle.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Please only include the strings you actually need. The Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform, and this will then be cached, so listing too many strings is very wasteful.&lt;br /&gt;
There are additional attributes supported by the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; list, you can find about them in the [[#Mobile.php_supported_options|Mobile.php supported options]] section.&lt;br /&gt;
===Step 2. Creating the main function===&lt;br /&gt;
The main function displays the current issued certificate (or several warnings if it’s not possible to issue a certificate). It also displays a link to view the dates of previously issued certificates.&lt;br /&gt;
&lt;br /&gt;
All the functions must be created in the plugin or subsystem &amp;lt;code&amp;gt;classes/output&amp;lt;/code&amp;gt; directory, the name of the class must be &amp;lt;code&amp;gt;mobile&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
For this example, the namespace name will be &amp;lt;code&amp;gt;mod_certificate\output&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace mod_certificate\output;&lt;br /&gt;
&lt;br /&gt;
use context_module;&lt;br /&gt;
use mod_certificate_external;&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Returns the certificate course view for the mobile app.&lt;br /&gt;
     *&lt;br /&gt;
     * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
     *&lt;br /&gt;
     * @return array       HTML, JS and other data.&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
        $args = (object) $args;&lt;br /&gt;
        $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
        // Capabilities check.&lt;br /&gt;
        require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
        $context = \context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
        require_capability(&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
        if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
            require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
        }&lt;br /&gt;
        $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
        // Get certificates from external (taking care of exceptions).&lt;br /&gt;
        try {&lt;br /&gt;
            $issued = \mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
            $certificates = \mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
            $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $issues = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Set timemodified for each certificate.&lt;br /&gt;
        foreach ($issues as $issue) {&lt;br /&gt;
            if (empty($issue-&amp;gt;timemodified)) {&lt;br /&gt;
                $issue-&amp;gt;timemodified = $issue-&amp;gt;timecreated;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $showget = true;&lt;br /&gt;
        if ($certificate-&amp;gt;requiredtime &amp;amp;&amp;amp; !has_capability(&#039;mod/certificate:manage&#039;, $context)) {&lt;br /&gt;
            if (certificate_get_course_time($certificate-&amp;gt;course) &amp;lt; ($certificate-&amp;gt;requiredtime * 60)) {&lt;br /&gt;
                $showget = false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $certificate-&amp;gt;name = format_string($certificate-&amp;gt;name);&lt;br /&gt;
        [$certificate-&amp;gt;intro, $certificate-&amp;gt;introformat] =&lt;br /&gt;
                external_format_text($certificate-&amp;gt;intro, $certificate-&amp;gt;introformat, $context-&amp;gt;id, &#039;mod_certificate&#039;, &#039;intro&#039;);&lt;br /&gt;
        $data = [&lt;br /&gt;
            &#039;certificate&#039; =&amp;gt; $certificate,&lt;br /&gt;
            &#039;showget&#039; =&amp;gt; $showget &amp;amp;&amp;amp; count($issues) &amp;gt; 0,&lt;br /&gt;
            &#039;issues&#039; =&amp;gt; $issues,&lt;br /&gt;
            &#039;issue&#039; =&amp;gt; $issues[0],&lt;br /&gt;
            &#039;numissues&#039; =&amp;gt; count($issues),&lt;br /&gt;
            &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
            &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;files&#039; =&amp;gt; $issues,&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Function declaration: &lt;br /&gt;
: The function name is the same as the one used in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file (&amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; field). There is only one argument, &amp;lt;code&amp;gt;$args&amp;lt;/code&amp;gt;, which is an array containing all the information sent by the mobile app (the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversionname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversioncode&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;applang&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appcustomurlscheme&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
&lt;br /&gt;
; Function implementation:&lt;br /&gt;
: In the first part of the function, we check permissions and capabilities (like a &amp;lt;code&amp;gt;view.php&amp;lt;/code&amp;gt; script would do normally). Then we retrieve the certificate information that’s necessary to display the template.&lt;br /&gt;
&lt;br /&gt;
; Function return:&lt;br /&gt;
* &amp;lt;code&amp;gt;templates&amp;lt;/code&amp;gt; — The rendered template (notice that we could return more than one template, but we usually would only need one). By default the app will always render the first template received, the rest of the templates can be used if the plugin defines some JavaScript code.&lt;br /&gt;
* &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; — Empty, because we don’t need any in this case.&lt;br /&gt;
* &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; — Empty as well, because we don’t need any additional data to be used by directives or components in the template. This field will be published as an object supporting 2-way data-binding in the template.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; — A list of files that the app should be able to download (for offline usage mostly).&lt;br /&gt;
===Step 3. Creating the template for the main function===&lt;br /&gt;
This is the most important part of your plugin because it contains the code that will be rendered on the mobile app.&lt;br /&gt;
&lt;br /&gt;
In this template we’ll be using Ionic, together with directives and components specific to the Moodle App.&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;ion-&amp;lt;/code&amp;gt; are ionic components. Most of the time, the component name is self-explanatory but you may refer to a detailed guide here: https://ionicframework.com/docs/components/&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;core-&amp;lt;/code&amp;gt; are custom components of the Moodle App.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_page.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;core-course-module-description description=&amp;quot;&amp;lt;% certificate.intro %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-course-module-description&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;ion-list-header&amp;gt;&lt;br /&gt;
            &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.summaryofattempts&#039; | translate }}&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-list-header&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-new-content title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; &lt;br /&gt;
                        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
                        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
                    {{ &#039;plugin.mod_certificate.viewcertificateviews&#039; | translate: {$a: &amp;lt;% numissues %&amp;gt;} }}&lt;br /&gt;
                &amp;lt;/ion-button&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&lt;br /&gt;
                        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
                        [files]=&amp;quot;[{&lt;br /&gt;
                            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
                            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
                            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
                        }]&amp;quot;&amp;gt;&lt;br /&gt;
                 &lt;br /&gt;
                        &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot; slot=&amp;quot;start&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                            {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
                    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
                &amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%^showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-label&amp;gt;&amp;lt;p&amp;gt;{{ &#039;plugin.mod_certificate.requiredtimenotmet&#039; | translate }}&amp;lt;/p&amp;gt;&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;!-- Call log WS when the template is loaded. --&amp;gt;&lt;br /&gt;
        &amp;lt;span core-site-plugins-call-ws-on-load name=&amp;quot;mod_certificate_view_certificate&amp;quot;&lt;br /&gt;
                [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/span&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache).&lt;br /&gt;
&lt;br /&gt;
Then we display the module description using &amp;lt;code&amp;gt;core-course-module-description&amp;lt;/code&amp;gt;, which is a component used to include the course module description.&lt;br /&gt;
&lt;br /&gt;
For displaying the certificate information we create a list of elements, adding a header on top.&lt;br /&gt;
&lt;br /&gt;
The following line using the &amp;lt;code&amp;gt;translate&amp;lt;/code&amp;gt; filter indicates that the app will translate the &amp;lt;code&amp;gt;summaryofattempts&amp;lt;/code&amp;gt; string id (here we could’ve used mustache translation but it is usually better to delegate the strings translations to the app). The string id has the following format: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
plugin.{plugin-identifier}.{string-id}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Where &amp;lt;code&amp;gt;{plugin-identifier}&amp;lt;/code&amp;gt; is taken from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;{string-id}&amp;lt;/code&amp;gt; must be indicated in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; field in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Then, we display a button to transition to another page if there are certificates issued. The attribute (directive) &amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt; indicates that if the user clicks the button, we need to call the &amp;lt;code&amp;gt;mobile_issues_view&amp;lt;/code&amp;gt; function in the &amp;lt;code&amp;gt;mod_certificate&amp;lt;/code&amp;gt; component; passing as arguments the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;. The content returned by this function will be displayed in a new page (read the following section to see the code of this new page).&lt;br /&gt;
&lt;br /&gt;
Just after this button, we display another one but this time for downloading an issued certificate. The &amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt; directive indicates that clicking this button is for downloading the whole activity and opening the main file. This means that, when the user clicks this button, the whole certificate activity will be available offline.&lt;br /&gt;
&lt;br /&gt;
Finally, just before the &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt; is closed, we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt; directive to indicate that once the page is loaded, we need to call a Web Service function in the server, in this case we are calling the &amp;lt;code&amp;gt;mod_certificate_view_certificate&amp;lt;/code&amp;gt; that will log that the user viewed this page.&lt;br /&gt;
&lt;br /&gt;
As you can see, no JavaScript was necessary at all. We used plain HTML elements and attributes that did all the complex dynamic logic (like calling a Web Service) behind the scenes.&lt;br /&gt;
&lt;br /&gt;
===Step 4. Adding an additional page===&lt;br /&gt;
Add the following method to &amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Returns the certificate issues view for the mobile app.&lt;br /&gt;
 * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
 *&lt;br /&gt;
 * @return array       HTML, JS and other data.&lt;br /&gt;
 */&lt;br /&gt;
public static function mobile_issues_view($args) {&lt;br /&gt;
    global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
    $args = (object) $args;&lt;br /&gt;
    $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
    // Capabilities check.&lt;br /&gt;
    require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
    $context = context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
    require_capability (&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
    if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
        require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
    }&lt;br /&gt;
    $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
    // Get certificates from external (taking care of exceptions).&lt;br /&gt;
    try {&lt;br /&gt;
        $issued = mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
        $certificates = mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
        $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
    } catch (Exception $e) {&lt;br /&gt;
        $issues = [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $data = [&#039;issues&#039; =&amp;gt; $issues];&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
            [&lt;br /&gt;
                &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_issues&#039;, $data),&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
        &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This method for the new page was added just after &amp;lt;code&amp;gt;mobile_course_view&amp;lt;/code&amp;gt;, the code is quite similar: checks the capabilities, retrieves the information required for the template, and returns the template rendered.&lt;br /&gt;
&lt;br /&gt;
The code of the mustache template is also very simple.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_issues.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &amp;lt;%timecreated%&amp;gt; | coreToLocaleString }}&amp;lt;/p&amp;gt;&lt;br /&gt;
                &amp;lt;p&amp;gt;&amp;lt;%grade%&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As we did in the previous template, in the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache). &lt;br /&gt;
&lt;br /&gt;
Here we are creating an Ionic list that will display a new item in the list per each issued certificated.&lt;br /&gt;
&lt;br /&gt;
For the issued certificated we’ll display the time when it was created (using the app filter &amp;lt;code&amp;gt;coreToLocaleString&amp;lt;/code&amp;gt;). We are also displaying the grade displayed in the certificate (if any).&lt;br /&gt;
===Step 5. Plugin webservices, if included===&lt;br /&gt;
If your plugin uses its own web services, they will also need to be enabled for mobile access in your &amp;lt;code&amp;gt;db/services.php&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
The following line should be included in each webservice definition:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;services&#039; =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/db/services.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$functions = [&lt;br /&gt;
    &#039;mod_certificate_get_certificates_by_courses&#039; =&amp;gt; [&lt;br /&gt;
        &#039;classname&#039;     =&amp;gt; &#039;mod_certificate_external&#039;,&lt;br /&gt;
        &#039;methodname&#039;    =&amp;gt; &#039;get_certificates_by_courses&#039;,&lt;br /&gt;
        &#039;description&#039;   =&amp;gt; &#039;Returns a list of certificate instances...&#039;,&lt;br /&gt;
        &#039;type&#039;          =&amp;gt; &#039;read&#039;,&lt;br /&gt;
        &#039;capabilities&#039;  =&amp;gt; &#039;mod/certificate:view&#039;,&lt;br /&gt;
        &#039;services&#039;      =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Mobile.php supported options==&lt;br /&gt;
In the previous section, we learned about some of the existing options for handlers configuration. This is the full list of supported options.&lt;br /&gt;
===Common options===&lt;br /&gt;
* &amp;lt;code&amp;gt;delegate&amp;lt;/code&amp;gt; (mandatory) — Name of the delegate to register the handler in.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (mandatory) — The method to call to retrieve the main page content.&lt;br /&gt;
* &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; (optional) — A method to call to retrieve the initialisation JS and the restrictions to apply to the whole handler. It can also return templates that can be used from JavaScript. You can learn more about this in the [[#Initialisation|Initialisation]] section.&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttocurrentuser&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForUser&amp;lt;/code&amp;gt; function. If true, the handler will only be shown for the current user. For more info about displaying the plugin only for certain users, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttoenrolledcourses&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForCourse&amp;lt;/code&amp;gt; function. If true or not defined, the handler will only be shown for courses the user is enrolled in. For more info about displaying the plugin only for certain courses, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;styles&amp;lt;/code&amp;gt; (optional) — An array with two properties: &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt;. The URL should point to a CSS file, either using an absolute URL or a relative URL. This file will be downloaded and applied by the app. It&#039;s recommended to include styles that will only affect your plugin templates. The version number is used to determine if the file needs to be downloaded again, you should change the version number everytime you change the CSS file.&lt;br /&gt;
* &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; (optional) — If your plugin supports a component in the app different than the one defined by your plugin, you can use this property to specify it. For example, you can create a local plugin to support a certain course format, activity, etc. The component of your plugin in Moodle would be &amp;lt;code&amp;gt;local_whatever&amp;lt;/code&amp;gt;, but in &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; you can specify that this handler will implement &amp;lt;code&amp;gt;format_whatever&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;mod_whatever&amp;lt;/code&amp;gt;. This property was introduced in the version 3.6.1 of the app.&lt;br /&gt;
===Options only for CoreMainMenuDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. Main Menu plugins are always displayed in the More tab, they cannot be displayed as tabs in the bottom bar.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreMainMenuHomeDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseOptionsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ismenuhandler&amp;lt;/code&amp;gt; (optional) — Supported from the 3.7.1 version of the app. Set it to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if you want your plugin to be displayed in the contextual menu of the course instead of in the top tabs. The contextual menu is displayed when you click in the 3-dots button at the top right of the course.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseModuleDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (optional) — The function to call to retrieve the main page content. In this delegate the method is optional. If the method is not set, the module won&#039;t be clickable.&lt;br /&gt;
* &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt; (optional) — List of functions to call when prefetching the module. It can be a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; method or a WS. You can filter the params received by the WS. By default, WS will receive these params: &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;. Other valid values that will be added if they are present in the list of params: &amp;lt;code&amp;gt;courseids&amp;lt;/code&amp;gt; (it will receive a list with the courses the user is enrolled in), &amp;lt;code&amp;gt;{component}id&amp;lt;/code&amp;gt; (For example, &amp;lt;code&amp;gt;certificateid&amp;lt;/code&amp;gt;).&lt;br /&gt;
* &amp;lt;code&amp;gt;downloadbutton&amp;lt;/code&amp;gt; (optional) — Whether to display download button in the module. If not defined, the button will be shown if there is any offlinefunction.&lt;br /&gt;
* &amp;lt;code&amp;gt;isresource&amp;lt;/code&amp;gt; (optional) — Whether the module is a resource or an activity. Only used if there is any offline function. If your module relies on the &amp;lt;code&amp;gt;contents&amp;lt;/code&amp;gt; field, then it should be &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;updatesnames&amp;lt;/code&amp;gt; (optional) — Only used if there is any offline function. A regular expression to check if there&#039;s any update in the module. It will be compared to the result of &amp;lt;code&amp;gt;core_course_check_updates&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayopeninbrowser&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Open in browser&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayOpenInBrowser = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydescription&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Description&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayDescription = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayrefresh&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Refresh&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayRefresh = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayprefetch&amp;lt;/code&amp;gt; (optional) — Whether the module should display the download option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayPrefetch = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysize&amp;lt;/code&amp;gt; (optional) — Whether the module should display the downloaded size in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displaySize = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; (optional) — It can be used to specify the supported features of the plugin. Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. It should be an array with features as keys (For example, &amp;lt;code&amp;gt;[FEATURE_NO_VIEW_LINK =&amp;gt; true&amp;lt;/code&amp;gt;). If you need to calculate this dynamically please see [[#Module_plugins:_dynamically_determine_if_a_feature_is_supported|Module plugins: dynamically determine if a feature is supported]]. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; (optional) — If set, this method will be called when the course is rendered and the HTML returned will be displayed in the course page for the module. Please notice the HTML returned should not contain directives or components, only default HTML. Supported from the 3.8 version of the app.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseFormatDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;canviewallsections&amp;lt;/code&amp;gt; (optional) — Whether the course format allows seeing all sections in a single page. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayenabledownload&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, it&#039;s no longer used.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysectionselector&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, use &#039;&#039;displaycourseindex&#039;&#039; instead.&lt;br /&gt;
*&amp;lt;code&amp;gt;displaycourseindex&amp;lt;/code&amp;gt; (optional) — Whether the default course index should be displayed. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
===Options only for CoreUserDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — The type of the addon. The values accepted are &amp;lt;code&amp;gt;&#039;newpage&#039;&amp;lt;/code&amp;gt; (default) and &amp;lt;code&amp;gt;&#039;communication&#039;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreSettingsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for AddonMessageOutputDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreBlockDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (optional):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section. If this is not supplied, it will default to &amp;lt;code&amp;gt;&#039;plugins.block_{block-name}.pluginname&#039;&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class. If this is not supplied, it will default to &amp;lt;code&amp;gt;block_{block-name}&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — Possible values are:&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;title&amp;quot;&amp;lt;/code&amp;gt; — Your block will only display the block title, and when it&#039;s clicked it will open a new page to display the block contents (the template returned by the block&#039;s method).&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;prerendered&amp;quot;&amp;lt;/code&amp;gt; — Your block will display the content and footer returned by the WebService to get the blocks (for example, &amp;lt;code&amp;gt;core_block_get_course_blocks&amp;lt;/code&amp;gt;), so your block&#039;s method will never be called.&lt;br /&gt;
*** Any other value — Your block will immediately call the method specified in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and it will use the template to render the block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fallback&amp;lt;/code&amp;gt; (optional) — This option allows you to specify a block to use in the app instead of your block. For example, you can make the app display the &amp;quot;My overview&amp;quot; block instead of your block in the app by setting &amp;lt;code&amp;gt;&#039;fallback&#039; =&amp;gt; &#039;myoverview&#039;&amp;lt;/code&amp;gt;. The fallback will only be used if you don&#039;t specify a &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; is different to &amp;lt;code&amp;gt;&#039;title&#039;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&#039;prerendered&#039;&amp;lt;/code&amp;gt;. Supported from the 3.9.0 version of the app.&lt;br /&gt;
==Delegates==&lt;br /&gt;
Delegates can be classified by type of plugin. For more info about type of plugins, please see the [[#Types_of_plugins|Types of plugins]] section.&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new items to the main menu (currently displayed at the bottom of the app). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuHomeDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new tabs in the home page (by default the app is displaying the &amp;quot;Dashboard&amp;quot; and &amp;quot;Site home&amp;quot; tabs). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new options in a course (Participants or Grades are examples of this type of delegate).&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting activity modules or resources.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add additional options in the user profile page in the app.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting course formats. When you open a course from the course list in the mobile app, it will check if there is a &amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt; handler for the format that site uses. If so, it will display the course using that handler. Otherwise, it will use the default app course format.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile course formats]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreSettingsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to add a new option in the settings page.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonMessageOutputDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a message output plugin.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreBlockDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a block. For example, blocks can be displayed in Site Home, Dashboard and the Course page.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question types.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile question types]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionBehaviourDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question behaviours.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting user profile fields.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModQuizAccessRuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a quiz access rule.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModAssignSubmissionDelegate&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;AddonModAssignFeedbackDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use these delegates to support assign submission or feedback plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonWorkshopAssessmentStrategyDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a workshop assessment strategy plugin.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
These delegates require JavaScript to be supported. See [[#Initialisation|Initialisation]] for more information.&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreContentLinksDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreCourseModulePrefetchDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFileUploaderDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CorePluginFileDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFilterDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
==Available components and directives==&lt;br /&gt;
===Difference between components and directives===&lt;br /&gt;
A directive is usually represented as an HTML attribute, allows you to extend a piece of HTML with additional information or functionality. Example of directives are: &amp;lt;code&amp;gt;core-auto-focus&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*ngIf&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ng-repeat&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components are also directives, but they are usually represented as an HTML tag and they are used to add custom elements to the app. Example of components are &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-item&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;core-search-box&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components and directives are Angular concepts; you can learn more about them and the components come out of the box with Ionic in the following links:&lt;br /&gt;
* [https://angular.io/guide/built-in-directives Angular directives documentation]&lt;br /&gt;
* [https://ionicframework.com/docs/components Ionic components]&lt;br /&gt;
===Custom core components and directives===&lt;br /&gt;
These are some useful custom components and directives that are only available in the Moodle App. Please note that this isn’t the full list of custom components and directives, it’s just an extract of the most common ones.&lt;br /&gt;
&lt;br /&gt;
You can find a full list of components and directives in the source code of the app, within [https://github.com/moodlehq/moodleapp/tree/master/src/core/components &amp;lt;code&amp;gt;src/core/components&amp;lt;/code&amp;gt;] and [https://github.com/moodlehq/moodleapp/tree/master/src/core/directives &amp;lt;code&amp;gt;src/core/directives&amp;lt;/code&amp;gt;].&lt;br /&gt;
====&amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;====&lt;br /&gt;
This directive formats the text and adds some directives needed for the app to work as it should. For example, it treats all links and all the embedded media so they work fine in the app. If some content in your template includes links or embedded media, please use this directive.&lt;br /&gt;
&lt;br /&gt;
This directive automatically applies &amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt; to all the links and embedded media.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;text&amp;lt;/code&amp;gt; (string) — The text to format.&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;adaptImg&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to adapt images to screen width. &lt;br /&gt;
* &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether all HTML tags should be removed.&lt;br /&gt;
* &amp;lt;code&amp;gt;singleLine&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether new lines should be removed to display all the text in single line. Only if &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; (number) — Optional. Max height in pixels to render the content box. The minimum accepted value is 50. Using this parameter will force &amp;lt;code&amp;gt;display: block&amp;lt;/code&amp;gt; to calculate the height better. If you want to avoid this, use &amp;lt;code&amp;gt;class=&amp;quot;inline&amp;quot;&amp;lt;/code&amp;gt; at the same time to use &amp;lt;code&amp;gt;display: inline-block&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;fullOnClick&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether it should open a new page with the full contents on click. Only if &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; is set and the content has been collapsed. &lt;br /&gt;
* &amp;lt;code&amp;gt;fullTitle&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;Description&amp;quot;&amp;lt;/code&amp;gt;. Title to use in full view.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-format-text text=&amp;quot;&amp;lt;% cm.description %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle a link. It performs several checks, like checking if the link needs to be opened in the app, and opens the link as it should (without overriding the app).&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;capture&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether the link needs to be captured by the app (check if the link can be handled by the app instead of opening it in a browser).&lt;br /&gt;
* &amp;lt;code&amp;gt;inApp&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to open in an embedded browser within the app or in the system browser.&lt;br /&gt;
* &amp;lt;code&amp;gt;autoLogin&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt;. If the link should be open with auto-login. Accepts the following values:&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;yes&amp;quot;&amp;lt;/code&amp;gt; — Always auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;no&amp;quot;&amp;lt;/code&amp;gt; — Never auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt; — Auto-login only if it points to the current site.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a href=&amp;quot;&amp;lt;% cm.url %&amp;gt;&amp;quot; core-link&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle links to files and embedded files. This directive should be used in any link to a file or any embedded file that you want to have available when the app is offline. &lt;br /&gt;
&lt;br /&gt;
If a file is downloaded, its URL will be replaced by the local file URL.&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;&amp;lt;% event.iconurl %&amp;gt;&amp;quot; core-external-content component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% event.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-user-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to go to user profile on click. When the user clicks the element where this directive is attached, the right user profile will be opened.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;userId&amp;lt;/code&amp;gt; (number) — User id to open the profile.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — Optional. Course id to show the user info related to that course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a ion-item core-user-link userId=&amp;quot;&amp;lt;% userid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to handle a remote file. It shows the file name, icon (depending on mime type) and a button to download or refresh it. The user can identify if the file is downloaded or not based on the button.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;file&amp;lt;/code&amp;gt; (object) — The file. Must have a &amp;lt;code&amp;gt;filename&amp;lt;/code&amp;gt; property and either &amp;lt;code&amp;gt;fileurl&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component the file belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDelete&amp;lt;/code&amp;gt; (boolean) — Optional. Whether the file can be deleted.&lt;br /&gt;
* &amp;lt;code&amp;gt;alwaysDownload&amp;lt;/code&amp;gt; (boolean) — Optional. Whether it should always display the refresh button when the file is downloaded. Use it for files that you cannot determine if they&#039;re outdated or not.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDownload&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether file can be downloaded. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-file&lt;br /&gt;
        [file]=&amp;quot;{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.url %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.name %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            filesize: &#039;&amp;lt;% issue.size %&amp;gt;&#039;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/core-file&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening a file. When the item with this directive is clicked, the file will be downloaded (if needed) and opened.&lt;br /&gt;
&lt;br /&gt;
It is usually recommended to use the &amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt; component since it also displays the state of the file.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt; (object) — The file to download.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. Component ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage (a button to download a file):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button&lt;br /&gt;
        [core-download-file]=&amp;quot;{&lt;br /&gt;
            fileurl: &amp;lt;% issue.url %&amp;gt;,&lt;br /&gt;
            timemodified: &amp;lt;% issue.timemodified %&amp;gt;,&lt;br /&gt;
            filesize: &amp;lt;% issue.size %&amp;gt;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.download | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening the main file of a module.&lt;br /&gt;
&lt;br /&gt;
When the item with this directive is clicked, the whole module will be downloaded (if needed) and its main file opened. This is meant for modules like &amp;lt;code&amp;gt;mod_resource&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This directive must receive either a &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; or a &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. If no files are provided, it will use &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; (object) — Optional, required if module is not supplied. The module object.&lt;br /&gt;
* &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt; (number) — Optional, required if module is not supplied. The module ID.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — The course ID the module belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional, defaults to the same value as &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. Component ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; (object[]) — Optional. List of files of the module. If not provided, uses &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot; &lt;br /&gt;
        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        [files]=&amp;quot;[{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
        }]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-navbar-buttons&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to add buttons to the app&#039;s header without having to place them inside the header itself. Using this component in a site plugin will allow adding buttons to the header of the current page.&lt;br /&gt;
&lt;br /&gt;
If this component indicates a position (start/end), the buttons will only be added if the header has some buttons in that position. If no start/end is specified, then the buttons will be added to the first &amp;lt;code&amp;gt;&amp;lt;ion-buttons&amp;gt;&amp;lt;/code&amp;gt; found in the header.&lt;br /&gt;
&lt;br /&gt;
You can use the &amp;lt;code&amp;gt;[hidden]&amp;lt;/code&amp;gt; input to hide all the inner buttons if a certain condition is met.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons end&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button (click)=&amp;quot;action()&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-icon slot=&amp;quot;icon-only&amp;quot; name=&amp;quot;funnel&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can also use this to add options to the context menu, for example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons&amp;gt;&lt;br /&gt;
    &amp;lt;core-context-menu&amp;gt;&lt;br /&gt;
        &amp;lt;core-context-menu-item&lt;br /&gt;
                [priority]=&amp;quot;500&amp;quot; content=&amp;quot;Nice boat&amp;quot; (action)=&amp;quot;boatFunction()&amp;quot;&lt;br /&gt;
                iconAction=&amp;quot;boat&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/core-context-menu-item&amp;gt;&lt;br /&gt;
    &amp;lt;/core-context-menu&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Using &#039;font&#039; icons with &amp;lt;code&amp;gt;ion-icon&amp;lt;/code&amp;gt;====&lt;br /&gt;
&lt;br /&gt;
Font icons are widely used on the app and Moodle LMS website. In order to support [https://fontawesome.com/v5/search?m=free font awesome icons]. We&#039;ve added a directive that uses prefixes on the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; attribute to use different font icons.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fas-&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;fa-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=solid Font awesome solid] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;far-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=regular Font awesome regular] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fab-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=brands Font awesome brands] library (But only a few are supported and we discourage to use them).&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;moodle-&amp;lt;/code&amp;gt; will use some svg icons [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/moodle imported from Moodle LMS].&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fam-&amp;lt;/code&amp;gt; will use [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/font-awesome customized font awesome icons].&lt;br /&gt;
* If the prefix is not found or not valid, the app will search the icon name on the [https://ionic.io/ionicons ionicons library].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example of usage to show icon &amp;quot;pizza-slice&amp;quot; from font-awesome regular library:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-icon name=&amp;quot;fas-pizza-slice&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We encourage the use of font-awesome icons to match the appearance from the LMS website version.&lt;br /&gt;
&lt;br /&gt;
===Specific component and directives for plugins===&lt;br /&gt;
These are component and directives created specifically for supporting Moodle plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to display a new content when clicked. This new content can be displayed in a new page or in the current page (only if the current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The params to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in v3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in same page or open a new one. &lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that doing &amp;lt;code&amp;gt;[useOtherData]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to go to a new content page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content &lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; &lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to load new content in current page using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot; samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked. The action to do when the WS call is successful depends on the provided data: display a message, go back or refresh current view.&lt;br /&gt;
&lt;br /&gt;
If you want to load a new content when the WS call is done, please see [[#core-site-plugins-call-ws-new-content|&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The params for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;successMessage&amp;lt;/code&amp;gt; (string) — Message to show on success. If not supplied, no message. If supplied but empty, defaults to &amp;quot;Success&amp;quot;.&lt;br /&gt;
* &amp;lt;code&amp;gt;goBackOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to go back if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to refresh the current view if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to send some data to the server without using cache, displaying default messages and refreshing on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage successMessage&lt;br /&gt;
        refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to send some data to the server using cache without confirming, going back on success and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        goBackOnSuccess=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Same as the previous example, but implementing custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;certificateViewed($event)&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.certificateViewed = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked and load a new content passing the WS result as arguments. This new content can be displayed in a new page or in the same page (only if current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to load some new content when done, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The parameters to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in the same page or open a new one.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. The format is the same as in &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;jsData&amp;lt;/code&amp;gt; (any) — JS variables to pass to the new page so they can be used in the template or JS. If &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; is supplied instead of an object, all initial variables from current page will be copied. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;newContentPreSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in 3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to get some data from the server without using cache, showing default confirm and displaying a new page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage&lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to get some data from the server using cache, without confirm, displaying new content in same page and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Same as the previous example, but implementing a custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS as soon as the template is loaded. This directive is meant for actions to do in the background, like calling logging Web Services.&lt;br /&gt;
&lt;br /&gt;
If you want to call a WS when the user clicks on a certain element, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span core-site-plugins-call-ws-on-load&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Advanced features==&lt;br /&gt;
===Display the plugin only if certain conditions are met===&lt;br /&gt;
You might want to display your plugin in the mobile app only if certain dynamic conditions are met, so the plugin would be displayed only for some users. This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section ahead).&lt;br /&gt;
&lt;br /&gt;
All initialisation methods are called as soon as your plugin is retrieved. If you don&#039;t want your plugin to be displayed for the current user, then you should return the following in the initialisation method (only for Moodle site 3.8 and onwards):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;disabled&#039; =&amp;gt; true];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If the Moodle site is older than 3.8, then the initialisation method should return this instead:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;javascript&#039; =&amp;gt; &#039;this.HANDLER_DISABLED&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
On the other hand, you might want to display a plugin only for certain courses (&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;) or only if the user is viewing certain users&#039; profiles (&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;). This can be achieved with the initialisation method too.&lt;br /&gt;
&lt;br /&gt;
In the initialisation method you can return a &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; property with two fields in it: &amp;lt;code&amp;gt;courses&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;users&amp;lt;/code&amp;gt;. If you return a list of courses IDs in this property, then your plugin will only be displayed when the user views any of those courses. In the same way, if you return a list of user IDs then your plugin will only be displayed when the user views any of those users&#039; profiles.&lt;br /&gt;
===Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;===&lt;br /&gt;
The values returned by the functions in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; are added to a variable so they can be used both in JavaScript and in templates. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call is added to a variable named &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt;, while the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call is added to a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call will be passed to the JS and template of all the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; calls in that handler. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call will only be passed to the JS and template returned by that &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call.&lt;br /&gt;
&lt;br /&gt;
This means that, in your JavaScript, you can access and use the data like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.CONTENT_OTHERDATA.myVar;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the template you could use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
{{ CONTENT_OTHERDATA.myVar }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; is the name we put to one of our variables, it can be any name that you want. In the example above, this is the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
[&#039;myVar&#039; =&amp;gt; &#039;Initial value&#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Example====&lt;br /&gt;
In our plugin, we want to display an input text with a certain initial value. When the user clicks a button, we want the value in the input to be sent to a certain Web Service. This can be done using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We will return the initial value of the input in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; of our PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;myVar&#039; =&amp;gt; &#039;My initial value&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then in the template we will use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item text-wrap&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label position=&amp;quot;stacked&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.textlabel | translate }}&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;text&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.myVar&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws name=&amp;quot;mod_certificate_my_webservice&amp;quot; [useOtherDataForWS]=&amp;quot;[&#039;myVar&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we are creating an input text and we use &amp;lt;code&amp;gt;[(ngModel)]&amp;lt;/code&amp;gt; to use the value in &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; as the initial value and to store the changes in the same &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This means that the initial value of the input will be &amp;quot;My initial value&amp;quot;, and if the user changes the value of the input these changes will be applied to the &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This is called 2-way data binding in Angular.&lt;br /&gt;
&lt;br /&gt;
Then we add a button to send this data to a WS, and for that we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive. We use the &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; attribute to specify which variable from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; we want to send to our WebService. So if the user enters &amp;quot;A new value&amp;quot; in the input and then clicks the button, it will call the WebService &amp;lt;code&amp;gt;mod_certificate_my_webservice&amp;lt;/code&amp;gt; and will send as a parameter &amp;lt;code&amp;gt;[&#039;myVar&#039; =&amp;gt; &#039;A new value&#039;]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We can also achieve the same result using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; attribute of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive instead of using &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws &lt;br /&gt;
        name=&amp;quot;mod_certificate_my_webservice&amp;quot; [params]=&amp;quot;{myVar: CONTENT_OTHERDATA.myVar}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The Web Service call will be exactly the same with both versions.&lt;br /&gt;
&lt;br /&gt;
Notice that this example could be done without using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; too, using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; input of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive.&lt;br /&gt;
===Running JS code after a content template has loaded===&lt;br /&gt;
When you return JavaScript code from a handler function using the &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; array key, this code is executed immediately after the web service call returns, which may be before the returned template has been rendered into the DOM. &lt;br /&gt;
&lt;br /&gt;
If your code needs to run after the DOM has been updated, you can use &amp;lt;code&amp;gt;setTimeout&amp;lt;/code&amp;gt; to call it. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&lt;br /&gt;
    &#039;template&#039; =&amp;gt; [&lt;br /&gt;
        // ...&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;javascript&#039; =&amp;gt; &#039;setTimeout(function() { console.log(&amp;quot;DOM is available now&amp;quot;); });&#039;,&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    &#039;files&#039; =&amp;gt; [],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice that if you wanted to write a lot of code here, you might be better off putting it in a function defined in the response from an initialisation template, so that it does not get loaded again with each page of content.&lt;br /&gt;
===JS functions visible in the templates===&lt;br /&gt;
The app provides some JavaScript functions that can be used from the templates to update, refresh or view content. These are the functions:&lt;br /&gt;
* &amp;lt;code&amp;gt;openContent(title: string, args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Open a new page to display some new content. You need to specify the &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; of the new page and the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshContent(showSpinner = true)&amp;lt;/code&amp;gt; — Refresh the current content. By default, it will display a spinner while refreshing. If you don&#039;t want it to be displayed, you should pass &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; as a parameter.&lt;br /&gt;
* &amp;lt;code&amp;gt;updateContent(args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Refresh the current content using different parameters. You need to specify the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Group selector=====&lt;br /&gt;
Imagine we have an activity that uses groups and we want to let the user select which group they want to see. A possible solution would be to return all the groups in the same template (hidden), and then show the group user selects. However, we can make it more dynamic and return only the group the user is requesting.&lt;br /&gt;
&lt;br /&gt;
To do so, we&#039;ll use a drop down to select the group. When the user selects a group using this drop down, we&#039;ll update the page content to display the new group.&lt;br /&gt;
&lt;br /&gt;
The main difficulty in this is to tell the view which group needs to be selected when the view is loaded. There are 2 ways to do it: using plain HTML or using Angular&#039;s &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;.&lt;br /&gt;
======Using plain HTML======&lt;br /&gt;
We need to add a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; attribute to the option that needs to be selected. To do so, we need to pre-caclulate the selected option in the PHP code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// Detect which group is selected.&lt;br /&gt;
foreach ($groups as $gid=&amp;gt;$group) {&lt;br /&gt;
    $group-&amp;gt;selected = $gid === $groupid;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$data = [&lt;br /&gt;
    &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
    &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
    &#039;groups&#039; =&amp;gt; $groups,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the code above, we&#039;re retrieving the groups the user can see and then we&#039;re adding a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; boolean to each one to determine which one needs to be selected in the drop down. Finally, we pass the list of groups to the template.&lt;br /&gt;
&lt;br /&gt;
In the template, we display the drop down like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: $event})&amp;quot; interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot; &amp;lt;%#selected%&amp;gt;selected&amp;lt;%/selected%&amp;gt; &amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The &amp;lt;code&amp;gt;ionChange&amp;lt;/code&amp;gt; function will be called every time the user selects a different group with the drop down. We&#039;re using the &amp;lt;code&amp;gt;updateContent&amp;lt;/code&amp;gt; function to update the current view using the new group. &amp;lt;code&amp;gt;$event&amp;lt;/code&amp;gt; is an Angular variable that will have the selected value (in our case, the group ID that was just selected). This is enough to make the group selector work.&lt;br /&gt;
======Using &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;======&lt;br /&gt;
&amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; is an Angular directive that allows storing the value of a certain input or select in a JavaScript variable, and also the opposite way: tell the input or select which value to set. The main problem is that we cannot initialise a JavaScript variable from the template, so we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the PHP function we&#039;ll return the group that needs to be selected in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
        &#039;group&#039; =&amp;gt; $groupid,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we don&#039;t need to iterate over the groups array like in the plain HTML example. However, now we&#039;re returning the group id in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array. As it&#039;s explained in the [[#Using_otherdata|Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;]] section, this &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; is visible in the templates inside a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. So in the template we&#039;ll use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.group&amp;quot;&lt;br /&gt;
        (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: CONTENT_OTHERDATA.group})&amp;quot;&lt;br /&gt;
        interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Use the rich text editor===&lt;br /&gt;
The rich text editor included in the app requires a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt; to work. You can use the &amp;lt;code&amp;gt;FormBuilder&amp;lt;/code&amp;gt; library to create this control (or to create a whole &amp;lt;cide&amp;gt;FormGroup if you prefer).&lt;br /&gt;
&lt;br /&gt;
With the following JavaScript you&#039;ll be able to create a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.control = this.FormBuilder.control(this.CONTENT_OTHERDATA.rte);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we&#039;re using a value returned in &amp;lt;code&amp;gt;OTHERDATA&amp;lt;/code&amp;gt; as the initial value of the rich text editor, but you can use whatever you want.&lt;br /&gt;
&lt;br /&gt;
Then you need to pass this control to the rich text editor in your template:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;core-rich-text-editor item-content [control]=&amp;quot;control&amp;quot; placeholder=&amp;quot;Enter your text here&amp;quot; name=&amp;quot;rte_answer&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-rich-text-editor&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, there are several ways to send the value in the rich text editor to a Web Service to save it. This is one of the simplest options:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_webservice&amp;quot; [params]=&amp;quot;{rte: control.value}&amp;quot; ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re passing the value of the rich text editor as a parameter to our Web Service.&lt;br /&gt;
===Initialisation===&lt;br /&gt;
All handlers can specify an &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; method in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. This method is meant to return some JavaScript code that needs to be executed as soon as the plugin is retrieved.&lt;br /&gt;
&lt;br /&gt;
When the app retrieves all the handlers, the first thing it will do is call the &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt; Web Service with the initialisation method. This WS call will only receive the default arguments.&lt;br /&gt;
&lt;br /&gt;
The app will immediately execute the JavaScript code returned by this WS call. This JavaScript can be used to manually register your handlers in the delegates you want, without having to rely on the default handlers built based on the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; data.&lt;br /&gt;
&lt;br /&gt;
The templates returned by this method will be added to a &amp;lt;code&amp;gt;INIT_TEMPLATES&amp;lt;/code&amp;gt; variable that will be passed to all the JavaScript code of that handler. This means that the JavaScript returned by the initialisation method or the main method can access any of the templates HTML like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.INIT_TEMPLATES[&#039;main&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In this case, &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is the ID of the template we want to use.&lt;br /&gt;
&lt;br /&gt;
The same happens with the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the initialisation method, it is added to an &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt; variable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; field returned by this call will be used to determine if your handler is enabled or not. For example, if your handler is for the delegate &amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt; and you return a list of course ids in &amp;lt;code&amp;gt;restrict.courses&amp;lt;/code&amp;gt;, then your handler will only be enabled in the courses you returned. This only applies to the default handlers, if you register your own handler using the JavaScript code then you should check yourself if the handler is enabled.&lt;br /&gt;
&lt;br /&gt;
Finally, if you return an object in this initialisation JavaScript code, all the properties of that object will be passed to all the JavaScript code of that handler so you can use them when the code is run. For example, if your JavaScript code does something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    MyAddonClass: new MyAddonClass()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then, for the rest of JavaScript code of your handler (for example, the main method) you can use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.MyAddonClass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Link handlers=====&lt;br /&gt;
A link handler allows you to decide what to do when a link with a certain URL is clicked. This is useful, for example, to open your plugin page when a link to your plugin is clicked.&lt;br /&gt;
&lt;br /&gt;
After the 4.0 version, the Moodle app automatically creates two link handlers for module plugins, you don&#039;t need to create them in your plugin&#039;s Javascript code anymore:&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginanme/view.php?id=X&#039;&#039;. When this link is clicked, it will open your module in the app.&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginname/index.php?id=X&#039;&#039;. When this link is clicked, it will open a page in the app listing all the modules of your type inside a certain course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Link handlers have some advanced features that allow you to change how links behave under different conditions.&lt;br /&gt;
======Patterns======&lt;br /&gt;
You can define a Regular Expression pattern to match certain links. This will apply the handler only to links that match the pattern.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = RegExp(&#039;\/mod\/foo\/specialpage.php&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Priority======&lt;br /&gt;
Multiple link handlers may apply to a given link. You can define the order of precedence by setting the priority; the handler with the highest priority will be used.&lt;br /&gt;
&lt;br /&gt;
All default handlers have a priority of 0, so 1 or higher will override the default.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Multiple actions======&lt;br /&gt;
Once a link has been matched, the handler&#039;s &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method determines what the link should do. This method has access to the URL and its parameters.&lt;br /&gt;
&lt;br /&gt;
Different actions can be returned depending on different conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                action: function(siteId, navCtrl) {&lt;br /&gt;
                    // The actual behaviour of the link goes here.&lt;br /&gt;
                },&lt;br /&gt;
                sites: [&lt;br /&gt;
                    // ...&lt;br /&gt;
                ],&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                // ...&lt;br /&gt;
            },&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once handlers have been matched for a link, the actions will be fetched for all the matching handlers, in priorty order. The first valid action will be used to open the link.&lt;br /&gt;
&lt;br /&gt;
If your handler is matched with a link, but a condition assessed in the &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method means you want to revert to the next highest priorty handler, you can invalidate your action by settings its sites propety to an empty array.&lt;br /&gt;
======Complex example======&lt;br /&gt;
This will match all URLs containing &amp;lt;code&amp;gt;/mod/foo/&amp;lt;/code&amp;gt;, and force those with an id parameter that&#039;s not in the &amp;lt;code&amp;gt;supportedModFoos&amp;lt;/code&amp;gt; array to open in the user&#039;s browser, rather than the app.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const that = this;&lt;br /&gt;
const supportedModFoos = [...];&lt;br /&gt;
&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = new RegExp(&#039;\/mod\/foo\/&#039;);&lt;br /&gt;
        this.name = &#039;AddonModFooLinkHandler&#039;;&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {     &lt;br /&gt;
        const action = {&lt;br /&gt;
            action() {&lt;br /&gt;
                that.CoreUtilsProvider.openInBrowser(url);&lt;br /&gt;
            },&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (supportedModFoos.indexOf(parseInt(params.id)) !== -1) {&lt;br /&gt;
            action.sites = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return [action];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreContentLinksDelegate.registerHandler(new AddonModFooLinkHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Module prefetch handler=====&lt;br /&gt;
The &amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt; handler allows you to define a list of offline functions to prefetch a module. However, you might want to create your own prefetch handler to determine what needs to be downloaded. For example, you might need to chain WS calls (pass the result of a WS call to the next one), and this cannot be done using offline functions.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to create a prefetch handler using the initialisation JS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Create a class that extends from CoreCourseActivityPrefetchHandlerBase.&lt;br /&gt;
class AddonModCertificateModulePrefetchHandler extends CoreCourseActivityPrefetchHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.name = &#039;AddonModCertificateModulePrefetchHandler&#039;;&lt;br /&gt;
        this.modName = &#039;certificate&#039;;&lt;br /&gt;
&lt;br /&gt;
        // This must match the plugin identifier from db/mobile.php,&lt;br /&gt;
        // otherwise the download link in the context menu will not update correctly.&lt;br /&gt;
        this.component = &#039;mod_certificate&#039;;&lt;br /&gt;
        this.updatesNames = /^configuration$|^.*files$/;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Override the prefetch call.&lt;br /&gt;
    prefetch(module, courseId, single, dirPath) {&lt;br /&gt;
        return this.prefetchPackage(module, courseId, single, prefetchCertificate);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prefetchCertificate(module, courseId, single, siteId) {&lt;br /&gt;
    // Perform all the WS calls.&lt;br /&gt;
    // You can access most of the app providers using that.ClassName. E.g. that.CoreWSProvider.call().&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseModulePrefetchDelegate.registerHandler(new AddonModCertificateModulePrefetchHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
One relatively simple full example is where you have a function that needs to work offline, but it has an additional argument other than the standard ones. You can imagine for this an activity like the book module, where it has multiple pages for the same &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;. The app will not automatically work with this situation — it will call the offline function with the standard arguments only — so you won&#039;t be able to prefetch all the possible parameters.&lt;br /&gt;
&lt;br /&gt;
To deal with this, you need to implement a web service in your Moodle component that returns the list of possible extra arguments, and then you can call this web service and loop around doing the same thing the app does when it prefetches the offline functions. Here is an example from a third-party module (showing only the actual prefetch function, the rest of the code is as above) where there are multiple values of a custom &amp;lt;code&amp;gt;section&amp;lt;/code&amp;gt; parameter for the mobile function &amp;lt;code&amp;gt;mobile_document_view&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function prefetchOucontent(module, courseId, single, siteId) {&lt;br /&gt;
    var component = &#039;mod_oucontent&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Get the site, first.&lt;br /&gt;
    return that.CoreSitesProvider.getSite(siteId).then(function(site) {&lt;br /&gt;
        // Read the list of pages in this document using a web service.&lt;br /&gt;
        return site.read(&#039;mod_oucontent_get_page_list&#039;, {&#039;cmid&#039;: module.id}).then(function(response) {&lt;br /&gt;
            var promises = [];&lt;br /&gt;
&lt;br /&gt;
            // For each page, read and process the page - this is a copy of logic in the app at&lt;br /&gt;
            // siteplugins.ts (prefetchFunctions), but modified to add the custom argument.&lt;br /&gt;
            for(var i = 0; i &amp;lt; response.length; i++) {&lt;br /&gt;
                var args = {&lt;br /&gt;
                    courseid: courseId,&lt;br /&gt;
                    cmid: module.id,&lt;br /&gt;
                    userid: site.getUserId()&lt;br /&gt;
                };&lt;br /&gt;
                if (response[i] !== &#039;&#039;) {&lt;br /&gt;
                    args.section = response[i];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                promises.push(that.CoreSitePluginsProvider.getContent(&lt;br /&gt;
                        component, &#039;mobile_document_view&#039;, args).then(&lt;br /&gt;
                        function(result) {&lt;br /&gt;
                            var subPromises = [];&lt;br /&gt;
                            if (result.files &amp;amp;&amp;amp; result.files.length) {&lt;br /&gt;
                                subPromises.push(that.CoreFilepoolProvider.downloadOrPrefetchFiles(&lt;br /&gt;
                                        site.id, result.files, true, false, component, module.id));&lt;br /&gt;
                            }&lt;br /&gt;
                            return Promise.all(subPromises);&lt;br /&gt;
                        }));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(promises);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Single activity course format=====&lt;br /&gt;
In the following example, the value of &amp;lt;code&amp;gt;INIT_TEMPLATES[&#039;main&#039;]&amp;lt;/code&amp;gt; is:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;componentClass&amp;quot; [data]=&amp;quot;data&amp;quot;&amp;gt;&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This template is returned by the initialisation method. And this is the JavaScript code returned:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatComponent {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.data = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ngOnChanges(changes) {&lt;br /&gt;
        var self = this;&lt;br /&gt;
&lt;br /&gt;
        if (this.course &amp;amp;&amp;amp; this.sections &amp;amp;&amp;amp; this.sections.length) {&lt;br /&gt;
            var module = this.sections[0] &amp;amp;&amp;amp; this.sections[0].modules &amp;amp;&amp;amp; this.sections[0].modules[0];&lt;br /&gt;
            if (module &amp;amp;&amp;amp; !this.componentClass) {&lt;br /&gt;
                that.CoreCourseModuleDelegate.getMainComponent(that.Injector, this.course, module).then((component) =&amp;gt; {&lt;br /&gt;
                    self.componentClass = component || that.CoreCourseUnsupportedModuleComponent;&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.data.courseId = this.course.id;&lt;br /&gt;
            this.data.module = module;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    doRefresh(refresher, done) {&lt;br /&gt;
        return Promise.resolve(this.dynamicComponent.callComponentFunction(&amp;quot;doRefresh&amp;quot;, [refresher, done]));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatHandler {&lt;br /&gt;
    &lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.name = &#039;singleactivity&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    isEnabled() {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canViewAllSections() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseTitle(course, sections) {&lt;br /&gt;
        if (sections &amp;amp;&amp;amp; sections[0] &amp;amp;&amp;amp; sections[0].modules &amp;amp;&amp;amp; sections[0].modules[0]) {&lt;br /&gt;
            return sections[0].modules[0].name;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return course.fullname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displayEnableDownload() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displaySectionSelector() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseFormatComponent() {&lt;br /&gt;
        return that.CoreCompileProvider.instantiateDynamicComponent(that.INIT_TEMPLATES[&#039;main&#039;], AddonSingleActivityFormatComponent);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseFormatDelegate.registerHandler(new AddonSingleActivityFormatHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using the JavaScript API===&lt;br /&gt;
The JavaScript API is only supported by the delegates specified in the [[#Templates_downloaded_on_login_and_rendered_using_JS_data_2|Templates downloaded on login and rendered using JS data]] section. This API allows you to override any of the functions of the default handler.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; specified in a handler registered in the &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; will be called immediately after the initialisation method, and the JavaScript returned by this method will be run. If this JavaScript code returns an object with certain functions, these functions will override the ones in the default handler.&lt;br /&gt;
&lt;br /&gt;
For example, if the JavaScript returned by the method returns something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The the &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function of the default handler will be overridden by the returned &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
The default handler for &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; only has 2 functions: &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt;. In addition, the JavaScript code can return an extra function named &amp;lt;code&amp;gt;componentInit&amp;lt;/code&amp;gt; that will be executed when the component returned by &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; is initialised.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to support the text user profile field using this API:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
var result = {&lt;br /&gt;
    componentInit: function() {&lt;br /&gt;
        if (this.field &amp;amp;&amp;amp; this.edit &amp;amp;&amp;amp; this.form) {&lt;br /&gt;
            this.field.modelName = &#039;profile_field_&#039; + this.field.shortname;&lt;br /&gt;
&lt;br /&gt;
            if (this.field.param2) {&lt;br /&gt;
                this.field.maxlength = parseInt(this.field.param2, 10) || &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.field.inputType = that.CoreUtilsProvider.isTrueOrOne(this.field.param3) ? &#039;password&#039; : &#039;text&#039;;&lt;br /&gt;
&lt;br /&gt;
            var formData = {&lt;br /&gt;
                value: this.field.defaultdata,&lt;br /&gt;
                disabled: this.disabled,&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            this.form.addControl(this.field.modelName,&lt;br /&gt;
                that.FormBuilder.control(formData, this.field.required &amp;amp;&amp;amp; !this.field.locked ? that.Validators.required : null));&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        var name = &#039;profile_field_&#039; + field.shortname;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            type: &amp;quot;text&amp;quot;,&lt;br /&gt;
            name: name,&lt;br /&gt;
            value: that.CoreTextUtilsProvider.cleanTags(formValues[name]),&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Translate dynamic strings===&lt;br /&gt;
If you wish to have an element that displays a localised string based on value from your template you can doing something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_myactivity.&amp;lt;% status %&amp;gt;&#039; | translate }}&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This could save you from having to write something like when only one value should be displayed:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        &amp;lt;%#isedting%&amp;gt;{{ &#039;plugin.mod_myactivity.editing&#039; | translate }}&amp;lt;%/isediting%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isopen%&amp;gt;{{ &#039;plugin.mod_myactivity.open&#039; | translate }}&amp;lt;%/isopen%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isclosed%&amp;gt;{{ &#039;plugin.mod_myactivity.closed&#039; | translate }}&amp;lt;%/isclosed%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using strings with dates===&lt;br /&gt;
If you have a string that you wish to pass a formatted date, for example in the Moodle language file you have:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;strwithdate&#039;] = &#039;This string includes a date of {$a-&amp;gt;date} in the middle of it.&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can localise the string correctly in your template using something like the following:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{ &#039;plugin.mod_myactivity.strwithdate&#039; | translate: {$a: { date: &amp;lt;% timestamp %&amp;gt; * 1000 | coreFormatDate: &amp;quot;dffulldate&amp;quot; } } }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A Unix timestamp must be multiplied by 1000 as the Mobile App expects millisecond timestamps, whereas Unix timestamps are in seconds.&lt;br /&gt;
===Support push notification clicks===&lt;br /&gt;
If your plugin sends push notifications to the app, you might want to open a certain page in the app when the notification is clicked. There are several ways to achieve this.&lt;br /&gt;
&lt;br /&gt;
The easiest way is to include a &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; in your notification. When the notification is clicked, the app will try to open the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; will also be displayed in web. If you want to use a specific URL for the app, different than the one displayed in web, you can do so by returning a &amp;lt;code&amp;gt;customdata&amp;lt;/code&amp;gt; array that contains an &amp;lt;code&amp;gt;appurl&amp;lt;/code&amp;gt; property:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$notification-&amp;gt;customdata = [&lt;br /&gt;
    &#039;appurl&#039; =&amp;gt; $myurl-&amp;gt;out(),&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In both cases you will have to create a link handler to treat the URL. For more info on how to create the link handler, please see [[#Advanced_link_handler|how to create an advanced link handler]].&lt;br /&gt;
&lt;br /&gt;
If you want to do something that only happens when the notification is clicked, not when the link is clicked, you&#039;ll have to implement a push click handler yourself. The way to create it is similar to [[#Advanced_link_handler|creating an advanced link handler]], but you&#039;ll have to use &amp;lt;code&amp;gt;CorePushNotificationsDelegate&amp;lt;/code&amp;gt; and your handler will have to implement the properties and functions defined in the [https://github.com/moodlehq/moodleapp/blob/master/src/core/features/pushnotifications/services/push-delegate.ts#L27 CorePushNotificationsClickHandler] interface.&lt;br /&gt;
===Implement a module similar to mod_label===&lt;br /&gt;
In Moodle 3.8 or higher, if your plugin doesn&#039;t support &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt; and you don&#039;t specify a &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; then the module will only display the module description in the course page and it won&#039;t be clickable in the app, just like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt;. You can decide if you want the module icon to be displayed or not (if you don&#039;t want it to be displayed, then don&#039;t define it in &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
However, if your plugin needs to work in previous versions of Moodle or you want to display something different than the description then you need a different approach.&lt;br /&gt;
&lt;br /&gt;
If your plugin wants to render something in the course page instead of just the module name and description you should specify the &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. The template returned by this method will be rendered in the course page. Please notice the HTML returned should not contain directives or components, only plain HTML.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want your module to be clickable then you just need to remove &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. With these 2 changes you can have a module that behaves like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt; in the app.&lt;br /&gt;
===Use Ionic navigation lifecycle functions===&lt;br /&gt;
Ionic let pages define some functions that will be called when certain navigation lifecycle events happen. For more info about these functions, see [https://ionicframework.com/docs/api/router-outlet Ionic&#039;s documentation].&lt;br /&gt;
&lt;br /&gt;
You can define these functions in your plugin javascript:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.ionViewWillLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In addition to that, you can also implement &amp;lt;code&amp;gt;canLeave&amp;lt;/code&amp;gt; to use Angular route guards:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.canLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So for example you can make your plugin ask for confirmation if the user tries to leave the page when he has some unsaved data.&lt;br /&gt;
===Module plugins: dynamically determine if a feature is supported===&lt;br /&gt;
In Moodle you can specify if your plugin supports a certain feature, like &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. If your plugin will always support or not a certain feature, then you can use the &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; to specify it ([[#Options_only_for_CoreCourseModuleDelegate|see more documentation about this]]). But if you need to calculate it dynamically then you will have to create a function to calculate it. &lt;br /&gt;
&lt;br /&gt;
This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section above). The JavaScript returned by your initialisation method will need to define a function named &amp;lt;code&amp;gt;supportsFeature&amp;lt;/code&amp;gt; that will receive the name of the feature:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    supportsFeature: function(featureName) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;.&lt;br /&gt;
== Testing ==&lt;br /&gt;
You can also write automated tests for your plugin using Behat, you can read more about it on the [[Acceptance testing for the Moodle App]] page.&lt;br /&gt;
== Upgrading plugins from an older version ==&lt;br /&gt;
If you added mobile support to your plugin for the Ionic 3 version of the app (previous to the 3.9.5 release), you will probably need to make some changes to make it compatible with Ionic 5.&lt;br /&gt;
&lt;br /&gt;
Learn more at the [[Moodle App Plugins Upgrade Guide]].&lt;br /&gt;
==Troubleshooting==&lt;br /&gt;
=== Invalid response received ===&lt;br /&gt;
You might receive this error when using the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive or similar. By default, the app expects all Web Service calls to return an object, if your Web Service returns another type (string, boolean, etc.) then you need to specify it using the &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; attribute of the directive. For example, if your WS returns a boolean value, then you should specify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{typeExpected: &#039;boolean&#039;}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In a similar way, if your Web Service returns &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; you need to tell the app not to expect any result using &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{responseExpected: false}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS ===&lt;br /&gt;
Some directives allow you to specify a form id or name to send the data from the form to a certain WS. These directives look for HTML inputs to retrieve the data to send. However, &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; don&#039;t use HTML inputs, they simulate them, so the directive isn&#039;t going to find their data and so it won&#039;t be sent to the Web Service.&lt;br /&gt;
&lt;br /&gt;
There are 2 workarounds to fix this problem.&lt;br /&gt;
==== Sending the data manually ====&lt;br /&gt;
The first solution is to send the missing params manually using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; property. We will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input value in a variable, and this variable will be passed to the parameters. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a template like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group name=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, responses: responses}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Basically, you need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to the affected element (in this case, the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt;). You can put whatever name you want as the value, we used &amp;quot;responses&amp;quot;. With this, every time the user selects a radio button the value will be stored in a variable called &amp;quot;responses&amp;quot;. Then, in the button we are passing this variable to the parameters of the Web Service.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute has priority over &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;, so if you have an input with &amp;lt;code&amp;gt;name=&amp;quot;responses&amp;quot;&amp;lt;/code&amp;gt; it will override what you&#039;re manually passing to &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;.&lt;br /&gt;
==== Using a hidden input ====&lt;br /&gt;
Since the directive is looking for HTML inputs, you need to add one with the value to send to the server. You can use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to synchronise your radio/checkbox/select with the new hidden input. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a radio button like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot; [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;hidden&amp;quot; [ngModel]=&amp;quot;responses&amp;quot; name=&amp;quot;responses&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we&#039;re using a variable called &amp;quot;responses&amp;quot; to synchronise the data between the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt; and the hidden input. You can use whatever name you want.&lt;br /&gt;
=== I can&#039;t return an object or array in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; ===&lt;br /&gt;
If you try to return an object or an array in any field inside &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, the Web Service call will fail with the following error:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Scalar type expected, array or object received&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Each field in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; must be a string, number or boolean; it cannot be an object or array. To make it work, you need to encode your object or array into a JSON string:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($data)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app will automatically parse this JSON and convert it back into an array or object.&lt;br /&gt;
==Examples==&lt;br /&gt;
===Accepting dynamic names in a Web Service===&lt;br /&gt;
We want to display a form where the names of the fields are dynamic, like it happens in quiz. This data will be sent to a new Web Service that we have created.&lt;br /&gt;
&lt;br /&gt;
The first issue we find is that the Web Service needs to define the names of the parameters received, but in this case they&#039;re dynamic. The solution is to accept an array of objects with name and value. So in the &amp;lt;code&amp;gt;_parameters()&amp;lt;/code&amp;gt; function of our new Web Service, we will add this parameter:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;data&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
     new external_single_structure(&lt;br /&gt;
        [&lt;br /&gt;
            &#039;name&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data name&#039;),&lt;br /&gt;
            &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data value&#039;),&lt;br /&gt;
        ]&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;The data to be saved&#039;, VALUE_DEFAULT, []&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now we need to adapt our form to send the data as the Web Service requires it. In our template, we have a button with the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive that will send the form data to our Web Service. To make this work we will have to pass the parameters manually, without using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute, because we need to format the data before it is sent.&lt;br /&gt;
&lt;br /&gt;
Since we will send the parameters manually and we want it all to be sent in the same array, we will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input data into a variable that we&#039;ll call &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;, but you can use the name you want. This variable will be an object that will hold the input data with the format &amp;quot;name-&amp;gt;value&amp;quot;. For example, if I have an input with name &amp;quot;a1&amp;quot; and value &amp;quot;My answer&amp;quot;, the data object will be:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{a1: &#039;My answer&#039;}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So we need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to all the inputs whose values need to be sent to the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; WS param. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-input name=&amp;quot;&amp;lt;% name %&amp;gt;&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.data[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re using &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; to store the data. We do it like this because we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the form, setting the values the user has already stored. If you don&#039;t need to initialise the form, then you can use the &amp;lt;code&amp;gt;dataObject&amp;lt;/code&amp;gt; variable, an empty object that the mobile app creates for you:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[(ngModel)]=&amp;quot;dataObject[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app has a function that allows you to convert this data object into an array like the one the WS expects: &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt;. So in our button we&#039;ll use this function to format the data before it&#039;s sent:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_ws_name&amp;quot;&lt;br /&gt;
    [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, data: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.data, &#039;name&#039;, &#039;value&#039;)}&amp;quot;&lt;br /&gt;
    successMessage&lt;br /&gt;
    refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see in the example above, we&#039;re specifying that the keys of the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object need to be stored in a property called &amp;quot;name&amp;quot;, and the values need to be stored in a property called &amp;quot;value&amp;quot;. If your Web Service expects different names you need to change the parameters of the &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
If you open your plugin now in the app it will display an error in the JavaScript console. The reason is that the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; variable doesn&#039;t exist inside &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. As it is explained in previous sections, &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; holds the data that you return in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; for your method. We&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the values to be displayed in the form.&lt;br /&gt;
&lt;br /&gt;
If the user hasn&#039;t answered the form yet, we can initialise the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object as an empty object. Please remember that we cannot return arrays or objects in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, so we&#039;ll return a JSON string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; &#039;{}&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
With the code above, the form will always be empty when the user opens it. But now we want to check if the user has already answered the form and fill the form with the previous values. We will do it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$userdata = get_user_responses(); // It will held the data in a format name-&amp;gt;value. Example: [&#039;a1&#039; =&amp;gt; &#039;My value&#039;].&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($userdata)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now the user will be able to see previous values when the form is opened, and clicking the button will send the data to our Web Service in array format.&lt;br /&gt;
==Moodle plugins with mobile support==&lt;br /&gt;
* Group choice: [https://moodle.org/plugins/mod_choicegroup Moodle plugins directory entry] and [https://github.com/ndunand/moodle-mod_choicegroup code in github].&lt;br /&gt;
* Custom certificate: [https://moodle.org/plugins/mod_customcert Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_customcert code in github].&lt;br /&gt;
* Gapfill question type: [https://moodle.org/plugins/qtype_gapfill Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_gapfill in github].&lt;br /&gt;
* Wordselect question type: [https://moodle.org/plugins/qtype_wordselect Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_wordselect in github].&lt;br /&gt;
* RegExp question type: [https://moodle.org/plugins/qtype_regexp Moodle plugins directory entry] and [https://github.com/rezeau/moodle-qtype_regexp in github].&lt;br /&gt;
* Certificate: [https://moodle.org/plugins/mod_certificate Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_certificate in github].&lt;br /&gt;
* Attendance [https://moodle.org/plugins/mod_attendance Moodle plugins directory entry] and [https://github.com/danmarsden/moodle-mod_attendance in github].&lt;br /&gt;
* ForumNG (unfinished support) [https://moodle.org/plugins/mod_forumng Moodle plugins directory entry] and [https://github.com/moodleou/moodle-mod_forumng in github].&lt;br /&gt;
* News block [https://github.com/moodleou/moodle-block_news in github].&lt;br /&gt;
* H5P activity module [https://moodle.org/plugins/mod_hvp Moodle plugins directory entry] and [https://github.com/h5p/h5p-moodle-plugin in github].&lt;br /&gt;
See the complete list in [https://moodle.org/plugins/browse.php?list=award&amp;amp;id=6 the plugins database] (it may contain some outdated plugins).&lt;br /&gt;
=== Mobile app support award ===&lt;br /&gt;
If you want your plugin to be awarded in the plugins directory and marked as supporting the mobile app, please feel encouraged to contact us via email at [mailto:mobile@moodle.com mobile@moodle.com].&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to include a link to your plugin page and the location of its code repository.&lt;br /&gt;
&lt;br /&gt;
See [https://moodle.org/plugins/?q=award:mobile-app the list of awarded plugins] in the plugins directory.&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Moodle App Ionic 5]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62054</id>
		<title>Moodle App Plugins Development Guide</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62054"/>
		<updated>2022-04-25T08:11:42Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: /* Using icons */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle App (Ionic 5)}}&lt;br /&gt;
If you want to add mobile support to your Moodle plugin, you can achieve it by extending different areas of the app using &#039;&#039;just PHP server side code&#039;&#039; and providing templates written with [https://ionicframework.com/docs/components Ionic] and custom components.&lt;br /&gt;
&lt;br /&gt;
You will have to:&lt;br /&gt;
# Create a &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file in your plugin. In this file, you will be able to indicate which areas of the app you want to extend. For example, adding a new option in the main menu, implementing support for a new activity module, including a new option in the course menu, including a new option in the user profile, etc. All the areas supported are described further in this document.&lt;br /&gt;
# Create new functions in a reserved namespace that will return the content of the new options. The content should be returned rendered as html. This html should use Ionic components so that it looks native, but it can be generated using mustache templates.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let’s clarify some points:&lt;br /&gt;
* You don’t need to create new Web Service functions (although you will be able to use them for advanced features). You just need plain php functions that will be placed in a reserved namespace.&lt;br /&gt;
* Those functions will be exported via the Web Service function &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt;.&lt;br /&gt;
* As arguments of your functions you will always receive the &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, some relevant details of the app (like the app version or the current language in the app), and some specific data depending on the type of plugin (&amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
* The mobile app also implements a list of custom Ionic components and directives that provide dynamic behaviour; like indicating that you are linking a file that can be downloaded, allowing a transition to new pages into the app calling a specific function in the server, submitting form data to the server, etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Getting started==&lt;br /&gt;
If you only want to write a plugin, it is not necessary that you set up your environment to work with the Moodle App. In fact, you don&#039;t even need to compile it. You can just [[Using the Moodle App in a browser|use a Chromium-based browser]] to add mobile support to your plugins!&lt;br /&gt;
&lt;br /&gt;
You can use the app from one of the hosted versions on [https://master.apps.moodledemo.net master.apps.moodledemo.net] (the latest stable version) and [https://integration.apps.moodledemo.net integration.apps.moodledemo.net] (the latest development version). If you need any specific environment (hosted versions are deployed with a &#039;&#039;production&#039;&#039; environment), you can also use [[Moodle App Docker Images|Docker images]]. And if you need to test your plugin in a native device, you can always use [https://download.moodle.org/mobile Moodle HQ&#039;s application].&lt;br /&gt;
&lt;br /&gt;
This should suffice for developing plugins. However, if you are working on advanced functionality and you need to run the application from the source code, you can find more information in the [[Moodle App Development Guide]].&lt;br /&gt;
===Development workflow===&lt;br /&gt;
Before getting into the specifics of your plugin, we recommend that you start adding a simple &amp;quot;Hello World&amp;quot; button in the app to see that everything works properly.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s say your plugin is called &amp;lt;code&amp;gt;local_hello&amp;lt;/code&amp;gt;, you can start by adding the following files:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;local_hello&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [&lt;br /&gt;
            &#039;hello&#039; =&amp;gt; [&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreMainMenuDelegate&#039;,&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;view_hello&#039;,&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;title&#039; =&amp;gt; &#039;hello&#039;,&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; &#039;earth&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [&lt;br /&gt;
            [&#039;hello&#039;, &#039;local_hello&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace local_hello\output;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    public static function view_hello() {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; &#039;&amp;lt;h1 class=&amp;quot;text-center&amp;quot;&amp;gt;{{ &amp;quot;plugin.local_hello.hello&amp;quot; | translate }}&amp;lt;/h1&amp;gt;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;lang/en/local_hello.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;hello&#039;] = &#039;Hello World&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once you&#039;ve done that, try logging into your site in the app and you should see a new button in the main menu or more menu (depending on the device) saying &amp;quot;Hello World&amp;quot;. If you press this button, you should see a page saying &amp;quot;Hello World!&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Congratualtions, you have written your first Moodle plugin with moodle support!&lt;br /&gt;
&lt;br /&gt;
You can read the rest of this page to learn more about mobile plugins and start working on your plugin. Here&#039;s some things to keep in mind:&lt;br /&gt;
* If you change the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file, you will have to refresh the browser. And remember to [https://developer.chrome.com/docs/devtools/network/reference/#disable-cache disable the network cache].&lt;br /&gt;
* If you change an existing template or function, you won’t have to refresh the browser. In most cases, doing a PTR (Pull To Refresh) in the page that displays the template will suffice.&lt;br /&gt;
* If any of these doesn&#039;t show your changes, you may need to [https://docs.moodle.org/311/en/Developer_tools#Purge_all_caches purge all caches] to avoid problems with the auto-loading cache.&lt;br /&gt;
* Ultimately, if that doesn&#039;t work either, you may have to log out from the site and log in again. If any changes affect plugin installation, you may also need to increase the version in your plugin&#039;s &amp;lt;code&amp;gt;version.php&amp;lt;/code&amp;gt; file and upgrade it in the site.&lt;br /&gt;
==Types of plugins==&lt;br /&gt;
There are 3 types of plugins:&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
[[File:Templates_downloaded_when_requested.png|thumb]]&lt;br /&gt;
With this type of plugin, the template of your plugin will be generated and downloaded when the user opens the plugin in the app. This means that your function will receive some context parameters. For example, if you&#039;re developing a course module plugin you will receive the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; (course module ID). You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
[[File:Templates_downloaded_on_login.png|thumb]]&lt;br /&gt;
With this type of plugin, the template for your plugin will be downloaded when the user logs in into the app and will be stored in the device. This means that your function will not receive any context parameters, and you need to return a generic template that will be built with JS data like the ones in the Moodle App. When the user opens a page that includes your plugin, your template will receive the required JS data and your template will be rendered. You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
You can always implement the whole plugin yourself using JavaScript instead of using our API. In fact, this is required if you want to implement some features like capturing links in the Mobile app. You can see the list of delegates that only support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
==Step by step example==&lt;br /&gt;
In this example, we are going to update an existing plugin, the [https://github.com/mdjnelson/moodle-mod_certificate Certificate activity module], that previously used a [[Moodle Mobile 2 (Ionic 1) Remote add-ons|Remote add-on]] (a legacy approach to implement mobile plugins).&lt;br /&gt;
&lt;br /&gt;
This is a simple activity module that displays the certificate issued for the current user along with the list of the dates of previously issued certificates. It also stores in the course log that the user viewed a certificate. This module also works offline: when the user downloads the course or activity, the data is pre-fetched and can be viewed offline.&lt;br /&gt;
===Step 1. Update the &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file===&lt;br /&gt;
In this case, we are updating an existing file. For new plugins, you should create this new file.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;mod_certificate&#039; =&amp;gt; [ // Plugin identifier&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;coursecertificate&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/mod/certificate/pix/icon.gif&#039;,&lt;br /&gt;
                    &#039;class&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
       &lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseModuleDelegate&#039;, // Delegate (where to display the link to the plugin)&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;mobile_course_view&#039;, // Main function in \mod_certificate\output\mobile&lt;br /&gt;
                &#039;offlinefunctions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;mobile_course_view&#039; =&amp;gt; [],&lt;br /&gt;
                    &#039;mobile_issues_view&#039; =&amp;gt; [],&lt;br /&gt;
                ], // Function that needs to be downloaded for offline.&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [ // Language strings that are used in all the handlers.&lt;br /&gt;
            [&#039;pluginname&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;summaryofattempts&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;getcertificate&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;requiredtimenotmet&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;viewcertificateviews&#039;, &#039;certificate&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Plugin identifier:&lt;br /&gt;
: A unique name for the plugin, it can be anything (there’s no need to match the module name).&lt;br /&gt;
&lt;br /&gt;
;Handlers (Different places where the plugin will display content):&lt;br /&gt;
: A plugin can be displayed in different views in the app. Each view should have a unique name inside the plugin scope (alphanumeric).&lt;br /&gt;
&lt;br /&gt;
; Display data:&lt;br /&gt;
: This is only needed for certain types of plugins. Also, depending on the type of delegate it may require additional (or less fields). In this case, we are indicating the module icon.&lt;br /&gt;
&lt;br /&gt;
; Delegate&lt;br /&gt;
: Where to display the link to the plugin, see the [[#Delegates|Delegates]] section for all the possible options.&lt;br /&gt;
&lt;br /&gt;
; Method:&lt;br /&gt;
: This is the method in the Moodle &amp;lt;code&amp;gt;\{component-name}\output\mobile&amp;lt;/code&amp;gt; class to be executed the first time the user clicks in the new option displayed in the app.&lt;br /&gt;
&lt;br /&gt;
; Offlinefunctions&lt;br /&gt;
: This is the list of functions that need to be called and stored when the user downloads a course for offline usage. Please note that you can add functions here that are not even listed in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. &lt;br /&gt;
: In our example, downloading for offline access will mean that we&#039;ll execute the functions for getting the certificate and issued certificates passing as parameters the current &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; (and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; when we are using the mod or course delegate). If we have the result of those functions stored in the app, we&#039;ll be able to display the certificate information even if the user is offline.&lt;br /&gt;
: Offline functions will be mostly used to display information for final users, any further interaction with the view won’t be supported offline (for example, trying to send information when the user is offline).&lt;br /&gt;
: You can indicate here other Web Services functions, indicating the parameters that they might need from a defined subset (currently &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Prefetching the module will also download all the files returned by the methods in these offline functions (in the &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; array).&lt;br /&gt;
: Note that if your functions use additional custom parameters (for example, if you implement multiple pages within a module&#039;s view function by using a &amp;lt;code&amp;gt;page&amp;lt;/code&amp;gt; parameter in addition to the usual &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;) then the app will not know which additional parameters to supply. In this case, do not list the function in &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt;; instead, you will need to manually implement a [[#Module_prefetch_handler|module prefetch handler]].&lt;br /&gt;
&lt;br /&gt;
;Lang:&lt;br /&gt;
: The language pack string ids used in the plugin by all the handlers. Normally these will be strings from your own plugin, however, you can list any strings you need here, like &amp;lt;code&amp;gt;[&#039;cancel&#039;, &#039;moodle&#039;]&amp;lt;/code&amp;gt;. If you do this, be warned that in the app you will then need to refer to that string as &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.myplugin.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (not &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.moodle.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Please only include the strings you actually need. The Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform, and this will then be cached, so listing too many strings is very wasteful.&lt;br /&gt;
There are additional attributes supported by the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; list, you can find about them in the [[#Mobile.php_supported_options|Mobile.php supported options]] section.&lt;br /&gt;
===Step 2. Creating the main function===&lt;br /&gt;
The main function displays the current issued certificate (or several warnings if it’s not possible to issue a certificate). It also displays a link to view the dates of previously issued certificates.&lt;br /&gt;
&lt;br /&gt;
All the functions must be created in the plugin or subsystem &amp;lt;code&amp;gt;classes/output&amp;lt;/code&amp;gt; directory, the name of the class must be &amp;lt;code&amp;gt;mobile&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
For this example, the namespace name will be &amp;lt;code&amp;gt;mod_certificate\output&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace mod_certificate\output;&lt;br /&gt;
&lt;br /&gt;
use context_module;&lt;br /&gt;
use mod_certificate_external;&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Returns the certificate course view for the mobile app.&lt;br /&gt;
     *&lt;br /&gt;
     * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
     *&lt;br /&gt;
     * @return array       HTML, JS and other data.&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
        $args = (object) $args;&lt;br /&gt;
        $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
        // Capabilities check.&lt;br /&gt;
        require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
        $context = \context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
        require_capability(&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
        if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
            require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
        }&lt;br /&gt;
        $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
        // Get certificates from external (taking care of exceptions).&lt;br /&gt;
        try {&lt;br /&gt;
            $issued = \mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
            $certificates = \mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
            $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $issues = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Set timemodified for each certificate.&lt;br /&gt;
        foreach ($issues as $issue) {&lt;br /&gt;
            if (empty($issue-&amp;gt;timemodified)) {&lt;br /&gt;
                $issue-&amp;gt;timemodified = $issue-&amp;gt;timecreated;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $showget = true;&lt;br /&gt;
        if ($certificate-&amp;gt;requiredtime &amp;amp;&amp;amp; !has_capability(&#039;mod/certificate:manage&#039;, $context)) {&lt;br /&gt;
            if (certificate_get_course_time($certificate-&amp;gt;course) &amp;lt; ($certificate-&amp;gt;requiredtime * 60)) {&lt;br /&gt;
                $showget = false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $certificate-&amp;gt;name = format_string($certificate-&amp;gt;name);&lt;br /&gt;
        [$certificate-&amp;gt;intro, $certificate-&amp;gt;introformat] =&lt;br /&gt;
                external_format_text($certificate-&amp;gt;intro, $certificate-&amp;gt;introformat, $context-&amp;gt;id, &#039;mod_certificate&#039;, &#039;intro&#039;);&lt;br /&gt;
        $data = [&lt;br /&gt;
            &#039;certificate&#039; =&amp;gt; $certificate,&lt;br /&gt;
            &#039;showget&#039; =&amp;gt; $showget &amp;amp;&amp;amp; count($issues) &amp;gt; 0,&lt;br /&gt;
            &#039;issues&#039; =&amp;gt; $issues,&lt;br /&gt;
            &#039;issue&#039; =&amp;gt; $issues[0],&lt;br /&gt;
            &#039;numissues&#039; =&amp;gt; count($issues),&lt;br /&gt;
            &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
            &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;files&#039; =&amp;gt; $issues,&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Function declaration: &lt;br /&gt;
: The function name is the same as the one used in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file (&amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; field). There is only one argument, &amp;lt;code&amp;gt;$args&amp;lt;/code&amp;gt;, which is an array containing all the information sent by the mobile app (the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversionname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversioncode&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;applang&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appcustomurlscheme&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
&lt;br /&gt;
; Function implementation:&lt;br /&gt;
: In the first part of the function, we check permissions and capabilities (like a &amp;lt;code&amp;gt;view.php&amp;lt;/code&amp;gt; script would do normally). Then we retrieve the certificate information that’s necessary to display the template.&lt;br /&gt;
&lt;br /&gt;
; Function return:&lt;br /&gt;
* &amp;lt;code&amp;gt;templates&amp;lt;/code&amp;gt; — The rendered template (notice that we could return more than one template, but we usually would only need one). By default the app will always render the first template received, the rest of the templates can be used if the plugin defines some JavaScript code.&lt;br /&gt;
* &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; — Empty, because we don’t need any in this case.&lt;br /&gt;
* &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; — Empty as well, because we don’t need any additional data to be used by directives or components in the template. This field will be published as an object supporting 2-way data-binding in the template.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; — A list of files that the app should be able to download (for offline usage mostly).&lt;br /&gt;
===Step 3. Creating the template for the main function===&lt;br /&gt;
This is the most important part of your plugin because it contains the code that will be rendered on the mobile app.&lt;br /&gt;
&lt;br /&gt;
In this template we’ll be using Ionic, together with directives and components specific to the Moodle App.&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;ion-&amp;lt;/code&amp;gt; are ionic components. Most of the time, the component name is self-explanatory but you may refer to a detailed guide here: https://ionicframework.com/docs/components/&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;core-&amp;lt;/code&amp;gt; are custom components of the Moodle App.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_page.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;core-course-module-description description=&amp;quot;&amp;lt;% certificate.intro %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-course-module-description&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;ion-list-header&amp;gt;&lt;br /&gt;
            &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.summaryofattempts&#039; | translate }}&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-list-header&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-new-content title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; &lt;br /&gt;
                        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
                        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
                    {{ &#039;plugin.mod_certificate.viewcertificateviews&#039; | translate: {$a: &amp;lt;% numissues %&amp;gt;} }}&lt;br /&gt;
                &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&lt;br /&gt;
                        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
                        [files]=&amp;quot;[{&lt;br /&gt;
                            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
                            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
                            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
                        }]&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot; item-start&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
                &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%^showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;p&amp;gt;{{ &#039;plugin.mod_certificate.requiredtimenotmet&#039; | translate }}&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;!-- Call log WS when the template is loaded. --&amp;gt;&lt;br /&gt;
        &amp;lt;span core-site-plugins-call-ws-on-load name=&amp;quot;mod_certificate_view_certificate&amp;quot;&lt;br /&gt;
                [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/span&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache).&lt;br /&gt;
&lt;br /&gt;
Then we display the module description using &amp;lt;code&amp;gt;core-course-module-description&amp;lt;/code&amp;gt;, which is a component used to include the course module description.&lt;br /&gt;
&lt;br /&gt;
For displaying the certificate information we create a list of elements, adding a header on top.&lt;br /&gt;
&lt;br /&gt;
The following line using the &amp;lt;code&amp;gt;translate&amp;lt;/code&amp;gt; filter indicates that the app will translate the &amp;lt;code&amp;gt;summaryofattempts&amp;lt;/code&amp;gt; string id (here we could’ve used mustache translation but it is usually better to delegate the strings translations to the app). The string id has the following format: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
plugin.{plugin-identifier}.{string-id}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Where &amp;lt;code&amp;gt;{plugin-identifier}&amp;lt;/code&amp;gt; is taken from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;{string-id}&amp;lt;/code&amp;gt; must be indicated in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; field in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Then, we display a button to transition to another page if there are certificates issued. The attribute (directive) &amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt; indicates that if the user clicks the button, we need to call the &amp;lt;code&amp;gt;mobile_issues_view&amp;lt;/code&amp;gt; function in the &amp;lt;code&amp;gt;mod_certificate&amp;lt;/code&amp;gt; component; passing as arguments the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;. The content returned by this function will be displayed in a new page (read the following section to see the code of this new page).&lt;br /&gt;
&lt;br /&gt;
Just after this button, we display another one but this time for downloading an issued certificate. The &amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt; directive indicates that clicking this button is for downloading the whole activity and opening the main file. This means that, when the user clicks this button, the whole certificate activity will be available offline.&lt;br /&gt;
&lt;br /&gt;
Finally, just before the &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt; is closed, we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt; directive to indicate that once the page is loaded, we need to call a Web Service function in the server, in this case we are calling the &amp;lt;code&amp;gt;mod_certificate_view_certificate&amp;lt;/code&amp;gt; that will log that the user viewed this page.&lt;br /&gt;
&lt;br /&gt;
As you can see, no JavaScript was necessary at all. We used plain HTML elements and attributes that did all the complex dynamic logic (like calling a Web Service) behind the scenes.&lt;br /&gt;
===Step 4. Adding an additional page===&lt;br /&gt;
Add the following method to &amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Returns the certificate issues view for the mobile app.&lt;br /&gt;
 * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
 *&lt;br /&gt;
 * @return array       HTML, JS and other data.&lt;br /&gt;
 */&lt;br /&gt;
public static function mobile_issues_view($args) {&lt;br /&gt;
    global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
    $args = (object) $args;&lt;br /&gt;
    $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
    // Capabilities check.&lt;br /&gt;
    require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
    $context = context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
    require_capability (&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
    if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
        require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
    }&lt;br /&gt;
    $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
    // Get certificates from external (taking care of exceptions).&lt;br /&gt;
    try {&lt;br /&gt;
        $issued = mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
        $certificates = mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
        $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
    } catch (Exception $e) {&lt;br /&gt;
        $issues = [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $data = [&#039;issues&#039; =&amp;gt; $issues];&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
            [&lt;br /&gt;
                &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_issues&#039;, $data),&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
        &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This method for the new page was added just after &amp;lt;code&amp;gt;mobile_course_view&amp;lt;/code&amp;gt;, the code is quite similar: checks the capabilities, retrieves the information required for the template, and returns the template rendered.&lt;br /&gt;
&lt;br /&gt;
The code of the mustache template is also very simple.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_issues.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &amp;lt;%timecreated%&amp;gt; | coreToLocaleString }}&amp;lt;/p&amp;gt;&lt;br /&gt;
                &amp;lt;p&amp;gt;&amp;lt;%grade%&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As we did in the previous template, in the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache). &lt;br /&gt;
&lt;br /&gt;
Here we are creating an Ionic list that will display a new item in the list per each issued certificated.&lt;br /&gt;
&lt;br /&gt;
For the issued certificated we’ll display the time when it was created (using the app filter &amp;lt;code&amp;gt;coreToLocaleString&amp;lt;/code&amp;gt;). We are also displaying the grade displayed in the certificate (if any).&lt;br /&gt;
===Step 5. Plugin webservices, if included===&lt;br /&gt;
If your plugin uses its own web services, they will also need to be enabled for mobile access in your &amp;lt;code&amp;gt;db/services.php&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
The following line should be included in each webservice definition:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;services&#039; =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/db/services.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$functions = [&lt;br /&gt;
    &#039;mod_certificate_get_certificates_by_courses&#039; =&amp;gt; [&lt;br /&gt;
        &#039;classname&#039;     =&amp;gt; &#039;mod_certificate_external&#039;,&lt;br /&gt;
        &#039;methodname&#039;    =&amp;gt; &#039;get_certificates_by_courses&#039;,&lt;br /&gt;
        &#039;description&#039;   =&amp;gt; &#039;Returns a list of certificate instances...&#039;,&lt;br /&gt;
        &#039;type&#039;          =&amp;gt; &#039;read&#039;,&lt;br /&gt;
        &#039;capabilities&#039;  =&amp;gt; &#039;mod/certificate:view&#039;,&lt;br /&gt;
        &#039;services&#039;      =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Mobile.php supported options==&lt;br /&gt;
In the previous section, we learned about some of the existing options for handlers configuration. This is the full list of supported options.&lt;br /&gt;
===Common options===&lt;br /&gt;
* &amp;lt;code&amp;gt;delegate&amp;lt;/code&amp;gt; (mandatory) — Name of the delegate to register the handler in.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (mandatory) — The method to call to retrieve the main page content.&lt;br /&gt;
* &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; (optional) — A method to call to retrieve the initialisation JS and the restrictions to apply to the whole handler. It can also return templates that can be used from JavaScript. You can learn more about this in the [[#Initialisation|Initialisation]] section.&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttocurrentuser&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForUser&amp;lt;/code&amp;gt; function. If true, the handler will only be shown for the current user. For more info about displaying the plugin only for certain users, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttoenrolledcourses&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForCourse&amp;lt;/code&amp;gt; function. If true or not defined, the handler will only be shown for courses the user is enrolled in. For more info about displaying the plugin only for certain courses, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;styles&amp;lt;/code&amp;gt; (optional) — An array with two properties: &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt;. The URL should point to a CSS file, either using an absolute URL or a relative URL. This file will be downloaded and applied by the app. It&#039;s recommended to include styles that will only affect your plugin templates. The version number is used to determine if the file needs to be downloaded again, you should change the version number everytime you change the CSS file.&lt;br /&gt;
* &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; (optional) — If your plugin supports a component in the app different than the one defined by your plugin, you can use this property to specify it. For example, you can create a local plugin to support a certain course format, activity, etc. The component of your plugin in Moodle would be &amp;lt;code&amp;gt;local_whatever&amp;lt;/code&amp;gt;, but in &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; you can specify that this handler will implement &amp;lt;code&amp;gt;format_whatever&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;mod_whatever&amp;lt;/code&amp;gt;. This property was introduced in the version 3.6.1 of the app.&lt;br /&gt;
===Options only for CoreMainMenuDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. Main Menu plugins are always displayed in the More tab, they cannot be displayed as tabs in the bottom bar.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreMainMenuHomeDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseOptionsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ismenuhandler&amp;lt;/code&amp;gt; (optional) — Supported from the 3.7.1 version of the app. Set it to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if you want your plugin to be displayed in the contextual menu of the course instead of in the top tabs. The contextual menu is displayed when you click in the 3-dots button at the top right of the course.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseModuleDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (optional) — The function to call to retrieve the main page content. In this delegate the method is optional. If the method is not set, the module won&#039;t be clickable.&lt;br /&gt;
* &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt; (optional) — List of functions to call when prefetching the module. It can be a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; method or a WS. You can filter the params received by the WS. By default, WS will receive these params: &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;. Other valid values that will be added if they are present in the list of params: &amp;lt;code&amp;gt;courseids&amp;lt;/code&amp;gt; (it will receive a list with the courses the user is enrolled in), &amp;lt;code&amp;gt;{component}id&amp;lt;/code&amp;gt; (For example, &amp;lt;code&amp;gt;certificateid&amp;lt;/code&amp;gt;).&lt;br /&gt;
* &amp;lt;code&amp;gt;downloadbutton&amp;lt;/code&amp;gt; (optional) — Whether to display download button in the module. If not defined, the button will be shown if there is any offlinefunction.&lt;br /&gt;
* &amp;lt;code&amp;gt;isresource&amp;lt;/code&amp;gt; (optional) — Whether the module is a resource or an activity. Only used if there is any offline function. If your module relies on the &amp;lt;code&amp;gt;contents&amp;lt;/code&amp;gt; field, then it should be &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;updatesnames&amp;lt;/code&amp;gt; (optional) — Only used if there is any offline function. A regular expression to check if there&#039;s any update in the module. It will be compared to the result of &amp;lt;code&amp;gt;core_course_check_updates&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayopeninbrowser&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Open in browser&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayOpenInBrowser = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydescription&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Description&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayDescription = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayrefresh&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Refresh&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayRefresh = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayprefetch&amp;lt;/code&amp;gt; (optional) — Whether the module should display the download option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayPrefetch = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysize&amp;lt;/code&amp;gt; (optional) — Whether the module should display the downloaded size in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displaySize = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; (optional) — It can be used to specify the supported features of the plugin. Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. It should be an array with features as keys (For example, &amp;lt;code&amp;gt;[FEATURE_NO_VIEW_LINK =&amp;gt; true&amp;lt;/code&amp;gt;). If you need to calculate this dynamically please see [[#Module_plugins:_dynamically_determine_if_a_feature_is_supported|Module plugins: dynamically determine if a feature is supported]]. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; (optional) — If set, this method will be called when the course is rendered and the HTML returned will be displayed in the course page for the module. Please notice the HTML returned should not contain directives or components, only default HTML. Supported from the 3.8 version of the app.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseFormatDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;canviewallsections&amp;lt;/code&amp;gt; (optional) — Whether the course format allows seeing all sections in a single page. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayenabledownload&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, it&#039;s no longer used.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysectionselector&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, use &#039;&#039;displaycourseindex&#039;&#039; instead.&lt;br /&gt;
*&amp;lt;code&amp;gt;displaycourseindex&amp;lt;/code&amp;gt; (optional) — Whether the default course index should be displayed. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
===Options only for CoreUserDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — The type of the addon. The values accepted are &amp;lt;code&amp;gt;&#039;newpage&#039;&amp;lt;/code&amp;gt; (default) and &amp;lt;code&amp;gt;&#039;communication&#039;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreSettingsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for AddonMessageOutputDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreBlockDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (optional):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section. If this is not supplied, it will default to &amp;lt;code&amp;gt;&#039;plugins.block_{block-name}.pluginname&#039;&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class. If this is not supplied, it will default to &amp;lt;code&amp;gt;block_{block-name}&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — Possible values are:&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;title&amp;quot;&amp;lt;/code&amp;gt; — Your block will only display the block title, and when it&#039;s clicked it will open a new page to display the block contents (the template returned by the block&#039;s method).&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;prerendered&amp;quot;&amp;lt;/code&amp;gt; — Your block will display the content and footer returned by the WebService to get the blocks (for example, &amp;lt;code&amp;gt;core_block_get_course_blocks&amp;lt;/code&amp;gt;), so your block&#039;s method will never be called.&lt;br /&gt;
*** Any other value — Your block will immediately call the method specified in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and it will use the template to render the block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fallback&amp;lt;/code&amp;gt; (optional) — This option allows you to specify a block to use in the app instead of your block. For example, you can make the app display the &amp;quot;My overview&amp;quot; block instead of your block in the app by setting &amp;lt;code&amp;gt;&#039;fallback&#039; =&amp;gt; &#039;myoverview&#039;&amp;lt;/code&amp;gt;. The fallback will only be used if you don&#039;t specify a &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; is different to &amp;lt;code&amp;gt;&#039;title&#039;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&#039;prerendered&#039;&amp;lt;/code&amp;gt;. Supported from the 3.9.0 version of the app.&lt;br /&gt;
==Delegates==&lt;br /&gt;
Delegates can be classified by type of plugin. For more info about type of plugins, please see the [[#Types_of_plugins|Types of plugins]] section.&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new items to the main menu (currently displayed at the bottom of the app). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuHomeDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new tabs in the home page (by default the app is displaying the &amp;quot;Dashboard&amp;quot; and &amp;quot;Site home&amp;quot; tabs). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new options in a course (Participants or Grades are examples of this type of delegate).&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting activity modules or resources.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add additional options in the user profile page in the app.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting course formats. When you open a course from the course list in the mobile app, it will check if there is a &amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt; handler for the format that site uses. If so, it will display the course using that handler. Otherwise, it will use the default app course format.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile course formats]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreSettingsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to add a new option in the settings page.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonMessageOutputDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a message output plugin.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreBlockDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a block. For example, blocks can be displayed in Site Home, Dashboard and the Course page.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question types.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile question types]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionBehaviourDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question behaviours.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting user profile fields.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModQuizAccessRuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a quiz access rule.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModAssignSubmissionDelegate&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;AddonModAssignFeedbackDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use these delegates to support assign submission or feedback plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonWorkshopAssessmentStrategyDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a workshop assessment strategy plugin.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
These delegates require JavaScript to be supported. See [[#Initialisation|Initialisation]] for more information.&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreContentLinksDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreCourseModulePrefetchDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFileUploaderDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CorePluginFileDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFilterDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
==Available components and directives==&lt;br /&gt;
===Difference between components and directives===&lt;br /&gt;
A directive is usually represented as an HTML attribute, allows you to extend a piece of HTML with additional information or functionality. Example of directives are: &amp;lt;code&amp;gt;core-auto-focus&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*ngIf&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ng-repeat&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components are also directives, but they are usually represented as an HTML tag and they are used to add custom elements to the app. Example of components are &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-item&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;core-search-box&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components and directives are Angular concepts; you can learn more about them and the components come out of the box with Ionic in the following links:&lt;br /&gt;
* [https://angular.io/guide/built-in-directives Angular directives documentation]&lt;br /&gt;
* [https://ionicframework.com/docs/components Ionic components]&lt;br /&gt;
===Custom core components and directives===&lt;br /&gt;
These are some useful custom components and directives that are only available in the Moodle App. Please note that this isn’t the full list of custom components and directives, it’s just an extract of the most common ones.&lt;br /&gt;
&lt;br /&gt;
You can find a full list of components and directives in the source code of the app, within [https://github.com/moodlehq/moodleapp/tree/master/src/core/components &amp;lt;code&amp;gt;src/core/components&amp;lt;/code&amp;gt;] and [https://github.com/moodlehq/moodleapp/tree/master/src/core/directives &amp;lt;code&amp;gt;src/core/directives&amp;lt;/code&amp;gt;].&lt;br /&gt;
====&amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;====&lt;br /&gt;
This directive formats the text and adds some directives needed for the app to work as it should. For example, it treats all links and all the embedded media so they work fine in the app. If some content in your template includes links or embedded media, please use this directive.&lt;br /&gt;
&lt;br /&gt;
This directive automatically applies &amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt; to all the links and embedded media.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;text&amp;lt;/code&amp;gt; (string) — The text to format.&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;adaptImg&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to adapt images to screen width. &lt;br /&gt;
* &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether all HTML tags should be removed.&lt;br /&gt;
* &amp;lt;code&amp;gt;singleLine&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether new lines should be removed to display all the text in single line. Only if &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; (number) — Optional. Max height in pixels to render the content box. The minimum accepted value is 50. Using this parameter will force &amp;lt;code&amp;gt;display: block&amp;lt;/code&amp;gt; to calculate the height better. If you want to avoid this, use &amp;lt;code&amp;gt;class=&amp;quot;inline&amp;quot;&amp;lt;/code&amp;gt; at the same time to use &amp;lt;code&amp;gt;display: inline-block&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;fullOnClick&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether it should open a new page with the full contents on click. Only if &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; is set and the content has been collapsed. &lt;br /&gt;
* &amp;lt;code&amp;gt;fullTitle&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;Description&amp;quot;&amp;lt;/code&amp;gt;. Title to use in full view.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-format-text text=&amp;quot;&amp;lt;% cm.description %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle a link. It performs several checks, like checking if the link needs to be opened in the app, and opens the link as it should (without overriding the app).&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;capture&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether the link needs to be captured by the app (check if the link can be handled by the app instead of opening it in a browser).&lt;br /&gt;
* &amp;lt;code&amp;gt;inApp&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to open in an embedded browser within the app or in the system browser.&lt;br /&gt;
* &amp;lt;code&amp;gt;autoLogin&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt;. If the link should be open with auto-login. Accepts the following values:&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;yes&amp;quot;&amp;lt;/code&amp;gt; — Always auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;no&amp;quot;&amp;lt;/code&amp;gt; — Never auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt; — Auto-login only if it points to the current site.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a href=&amp;quot;&amp;lt;% cm.url %&amp;gt;&amp;quot; core-link&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle links to files and embedded files. This directive should be used in any link to a file or any embedded file that you want to have available when the app is offline. &lt;br /&gt;
&lt;br /&gt;
If a file is downloaded, its URL will be replaced by the local file URL.&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;&amp;lt;% event.iconurl %&amp;gt;&amp;quot; core-external-content component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% event.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-user-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to go to user profile on click. When the user clicks the element where this directive is attached, the right user profile will be opened.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;userId&amp;lt;/code&amp;gt; (number) — User id to open the profile.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — Optional. Course id to show the user info related to that course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a ion-item core-user-link userId=&amp;quot;&amp;lt;% userid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to handle a remote file. It shows the file name, icon (depending on mime type) and a button to download or refresh it. The user can identify if the file is downloaded or not based on the button.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;file&amp;lt;/code&amp;gt; (object) — The file. Must have a &amp;lt;code&amp;gt;filename&amp;lt;/code&amp;gt; property and either &amp;lt;code&amp;gt;fileurl&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component the file belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDelete&amp;lt;/code&amp;gt; (boolean) — Optional. Whether the file can be deleted.&lt;br /&gt;
* &amp;lt;code&amp;gt;alwaysDownload&amp;lt;/code&amp;gt; (boolean) — Optional. Whether it should always display the refresh button when the file is downloaded. Use it for files that you cannot determine if they&#039;re outdated or not.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDownload&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether file can be downloaded. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-file&lt;br /&gt;
        [file]=&amp;quot;{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.url %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.name %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            filesize: &#039;&amp;lt;% issue.size %&amp;gt;&#039;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/core-file&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening a file. When the item with this directive is clicked, the file will be downloaded (if needed) and opened.&lt;br /&gt;
&lt;br /&gt;
It is usually recommended to use the &amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt; component since it also displays the state of the file.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt; (object) — The file to download.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. Component ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage (a button to download a file):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button&lt;br /&gt;
        [core-download-file]=&amp;quot;{&lt;br /&gt;
            fileurl: &amp;lt;% issue.url %&amp;gt;,&lt;br /&gt;
            timemodified: &amp;lt;% issue.timemodified %&amp;gt;,&lt;br /&gt;
            filesize: &amp;lt;% issue.size %&amp;gt;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.download | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening the main file of a module.&lt;br /&gt;
&lt;br /&gt;
When the item with this directive is clicked, the whole module will be downloaded (if needed) and its main file opened. This is meant for modules like &amp;lt;code&amp;gt;mod_resource&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This directive must receive either a &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; or a &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. If no files are provided, it will use &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; (object) — Optional, required if module is not supplied. The module object.&lt;br /&gt;
* &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt; (number) — Optional, required if module is not supplied. The module ID.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — The course ID the module belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional, defaults to the same value as &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. Component ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; (object[]) — Optional. List of files of the module. If not provided, uses &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot; &lt;br /&gt;
        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        [files]=&amp;quot;[{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
        }]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-navbar-buttons&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to add buttons to the app&#039;s header without having to place them inside the header itself. Using this component in a site plugin will allow adding buttons to the header of the current page.&lt;br /&gt;
&lt;br /&gt;
If this component indicates a position (start/end), the buttons will only be added if the header has some buttons in that position. If no start/end is specified, then the buttons will be added to the first &amp;lt;code&amp;gt;&amp;lt;ion-buttons&amp;gt;&amp;lt;/code&amp;gt; found in the header.&lt;br /&gt;
&lt;br /&gt;
You can use the &amp;lt;code&amp;gt;[hidden]&amp;lt;/code&amp;gt; input to hide all the inner buttons if a certain condition is met.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons end&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button (click)=&amp;quot;action()&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-icon slot=&amp;quot;icon-only&amp;quot; name=&amp;quot;funnel&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can also use this to add options to the context menu, for example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons&amp;gt;&lt;br /&gt;
    &amp;lt;core-context-menu&amp;gt;&lt;br /&gt;
        &amp;lt;core-context-menu-item&lt;br /&gt;
                [priority]=&amp;quot;500&amp;quot; content=&amp;quot;Nice boat&amp;quot; (action)=&amp;quot;boatFunction()&amp;quot;&lt;br /&gt;
                iconAction=&amp;quot;boat&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/core-context-menu-item&amp;gt;&lt;br /&gt;
    &amp;lt;/core-context-menu&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Using &#039;font&#039; icons with &amp;lt;code&amp;gt;ion-icon&amp;lt;/code&amp;gt;====&lt;br /&gt;
&lt;br /&gt;
Font icons are widely used on the app and Moodle LMS website. In order to support [https://fontawesome.com/v5/search?m=free font awesome icons]. We&#039;ve added a directive that uses prefixes on the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; attribute to use different font icons.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fas-&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;fa-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=solid Font awesome solid] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;far-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=regular Font awesome regular] library.&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fab-&amp;lt;/code&amp;gt; will use [https://fontawesome.com/v5/search?m=free&amp;amp;s=brands Font awesome brands] library (But only a few are supported and we discourage to use them).&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;moodle-&amp;lt;/code&amp;gt; will use some svg icons [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/moodle imported from Moodle LMS].&lt;br /&gt;
* Name prefixed with &amp;lt;code&amp;gt;fam-&amp;lt;/code&amp;gt; will use [https://github.com/moodlehq/moodleapp/tree/master/src/assets/fonts/moodle/font-awesome customized font awesome icons].&lt;br /&gt;
* If the prefix is not found or not valid, the app will search the icon name on the [https://ionic.io/ionicons ionicons library].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example of usage to show icon &amp;quot;pizza-slice&amp;quot; from font-awesome regular library:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-icon name=&amp;quot;fas-pizza-slice&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We encourage the use of font-awesome icons to match the appearance from the LMS website version.&lt;br /&gt;
&lt;br /&gt;
===Specific component and directives for plugins===&lt;br /&gt;
These are component and directives created specifically for supporting Moodle plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to display a new content when clicked. This new content can be displayed in a new page or in the current page (only if the current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The params to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in v3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in same page or open a new one. &lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that doing &amp;lt;code&amp;gt;[useOtherData]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to go to a new content page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content &lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; &lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to load new content in current page using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot; samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked. The action to do when the WS call is successful depends on the provided data: display a message, go back or refresh current view.&lt;br /&gt;
&lt;br /&gt;
If you want to load a new content when the WS call is done, please see [[#core-site-plugins-call-ws-new-content|&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The params for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;successMessage&amp;lt;/code&amp;gt; (string) — Message to show on success. If not supplied, no message. If supplied but empty, defaults to &amp;quot;Success&amp;quot;.&lt;br /&gt;
* &amp;lt;code&amp;gt;goBackOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to go back if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to refresh the current view if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to send some data to the server without using cache, displaying default messages and refreshing on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage successMessage&lt;br /&gt;
        refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to send some data to the server using cache without confirming, going back on success and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        goBackOnSuccess=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Same as the previous example, but implementing custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;certificateViewed($event)&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.certificateViewed = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked and load a new content passing the WS result as arguments. This new content can be displayed in a new page or in the same page (only if current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to load some new content when done, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The parameters to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in the same page or open a new one.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. The format is the same as in &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;jsData&amp;lt;/code&amp;gt; (any) — JS variables to pass to the new page so they can be used in the template or JS. If &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; is supplied instead of an object, all initial variables from current page will be copied. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;newContentPreSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in 3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to get some data from the server without using cache, showing default confirm and displaying a new page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage&lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to get some data from the server using cache, without confirm, displaying new content in same page and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Same as the previous example, but implementing a custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS as soon as the template is loaded. This directive is meant for actions to do in the background, like calling logging Web Services.&lt;br /&gt;
&lt;br /&gt;
If you want to call a WS when the user clicks on a certain element, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span core-site-plugins-call-ws-on-load&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Advanced features==&lt;br /&gt;
===Display the plugin only if certain conditions are met===&lt;br /&gt;
You might want to display your plugin in the mobile app only if certain dynamic conditions are met, so the plugin would be displayed only for some users. This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section ahead).&lt;br /&gt;
&lt;br /&gt;
All initialisation methods are called as soon as your plugin is retrieved. If you don&#039;t want your plugin to be displayed for the current user, then you should return the following in the initialisation method (only for Moodle site 3.8 and onwards):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;disabled&#039; =&amp;gt; true];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If the Moodle site is older than 3.8, then the initialisation method should return this instead:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;javascript&#039; =&amp;gt; &#039;this.HANDLER_DISABLED&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
On the other hand, you might want to display a plugin only for certain courses (&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;) or only if the user is viewing certain users&#039; profiles (&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;). This can be achieved with the initialisation method too.&lt;br /&gt;
&lt;br /&gt;
In the initialisation method you can return a &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; property with two fields in it: &amp;lt;code&amp;gt;courses&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;users&amp;lt;/code&amp;gt;. If you return a list of courses IDs in this property, then your plugin will only be displayed when the user views any of those courses. In the same way, if you return a list of user IDs then your plugin will only be displayed when the user views any of those users&#039; profiles.&lt;br /&gt;
===Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;===&lt;br /&gt;
The values returned by the functions in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; are added to a variable so they can be used both in JavaScript and in templates. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call is added to a variable named &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt;, while the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call is added to a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call will be passed to the JS and template of all the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; calls in that handler. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call will only be passed to the JS and template returned by that &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call.&lt;br /&gt;
&lt;br /&gt;
This means that, in your JavaScript, you can access and use the data like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.CONTENT_OTHERDATA.myVar;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the template you could use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
{{ CONTENT_OTHERDATA.myVar }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; is the name we put to one of our variables, it can be any name that you want. In the example above, this is the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
[&#039;myVar&#039; =&amp;gt; &#039;Initial value&#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Example====&lt;br /&gt;
In our plugin, we want to display an input text with a certain initial value. When the user clicks a button, we want the value in the input to be sent to a certain Web Service. This can be done using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We will return the initial value of the input in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; of our PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;myVar&#039; =&amp;gt; &#039;My initial value&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then in the template we will use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item text-wrap&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label position=&amp;quot;stacked&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.textlabel | translate }}&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;text&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.myVar&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws name=&amp;quot;mod_certificate_my_webservice&amp;quot; [useOtherDataForWS]=&amp;quot;[&#039;myVar&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we are creating an input text and we use &amp;lt;code&amp;gt;[(ngModel)]&amp;lt;/code&amp;gt; to use the value in &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; as the initial value and to store the changes in the same &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This means that the initial value of the input will be &amp;quot;My initial value&amp;quot;, and if the user changes the value of the input these changes will be applied to the &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This is called 2-way data binding in Angular.&lt;br /&gt;
&lt;br /&gt;
Then we add a button to send this data to a WS, and for that we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive. We use the &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; attribute to specify which variable from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; we want to send to our WebService. So if the user enters &amp;quot;A new value&amp;quot; in the input and then clicks the button, it will call the WebService &amp;lt;code&amp;gt;mod_certificate_my_webservice&amp;lt;/code&amp;gt; and will send as a parameter &amp;lt;code&amp;gt;[&#039;myVar&#039; =&amp;gt; &#039;A new value&#039;]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We can also achieve the same result using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; attribute of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive instead of using &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws &lt;br /&gt;
        name=&amp;quot;mod_certificate_my_webservice&amp;quot; [params]=&amp;quot;{myVar: CONTENT_OTHERDATA.myVar}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The Web Service call will be exactly the same with both versions.&lt;br /&gt;
&lt;br /&gt;
Notice that this example could be done without using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; too, using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; input of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive.&lt;br /&gt;
===Running JS code after a content template has loaded===&lt;br /&gt;
When you return JavaScript code from a handler function using the &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; array key, this code is executed immediately after the web service call returns, which may be before the returned template has been rendered into the DOM. &lt;br /&gt;
&lt;br /&gt;
If your code needs to run after the DOM has been updated, you can use &amp;lt;code&amp;gt;setTimeout&amp;lt;/code&amp;gt; to call it. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&lt;br /&gt;
    &#039;template&#039; =&amp;gt; [&lt;br /&gt;
        // ...&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;javascript&#039; =&amp;gt; &#039;setTimeout(function() { console.log(&amp;quot;DOM is available now&amp;quot;); });&#039;,&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    &#039;files&#039; =&amp;gt; [],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice that if you wanted to write a lot of code here, you might be better off putting it in a function defined in the response from an initialisation template, so that it does not get loaded again with each page of content.&lt;br /&gt;
===JS functions visible in the templates===&lt;br /&gt;
The app provides some JavaScript functions that can be used from the templates to update, refresh or view content. These are the functions:&lt;br /&gt;
* &amp;lt;code&amp;gt;openContent(title: string, args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Open a new page to display some new content. You need to specify the &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; of the new page and the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshContent(showSpinner = true)&amp;lt;/code&amp;gt; — Refresh the current content. By default, it will display a spinner while refreshing. If you don&#039;t want it to be displayed, you should pass &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; as a parameter.&lt;br /&gt;
* &amp;lt;code&amp;gt;updateContent(args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Refresh the current content using different parameters. You need to specify the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Group selector=====&lt;br /&gt;
Imagine we have an activity that uses groups and we want to let the user select which group they want to see. A possible solution would be to return all the groups in the same template (hidden), and then show the group user selects. However, we can make it more dynamic and return only the group the user is requesting.&lt;br /&gt;
&lt;br /&gt;
To do so, we&#039;ll use a drop down to select the group. When the user selects a group using this drop down, we&#039;ll update the page content to display the new group.&lt;br /&gt;
&lt;br /&gt;
The main difficulty in this is to tell the view which group needs to be selected when the view is loaded. There are 2 ways to do it: using plain HTML or using Angular&#039;s &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;.&lt;br /&gt;
======Using plain HTML======&lt;br /&gt;
We need to add a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; attribute to the option that needs to be selected. To do so, we need to pre-caclulate the selected option in the PHP code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// Detect which group is selected.&lt;br /&gt;
foreach ($groups as $gid=&amp;gt;$group) {&lt;br /&gt;
    $group-&amp;gt;selected = $gid === $groupid;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$data = [&lt;br /&gt;
    &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
    &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
    &#039;groups&#039; =&amp;gt; $groups,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the code above, we&#039;re retrieving the groups the user can see and then we&#039;re adding a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; boolean to each one to determine which one needs to be selected in the drop down. Finally, we pass the list of groups to the template.&lt;br /&gt;
&lt;br /&gt;
In the template, we display the drop down like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: $event})&amp;quot; interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot; &amp;lt;%#selected%&amp;gt;selected&amp;lt;%/selected%&amp;gt; &amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The &amp;lt;code&amp;gt;ionChange&amp;lt;/code&amp;gt; function will be called every time the user selects a different group with the drop down. We&#039;re using the &amp;lt;code&amp;gt;updateContent&amp;lt;/code&amp;gt; function to update the current view using the new group. &amp;lt;code&amp;gt;$event&amp;lt;/code&amp;gt; is an Angular variable that will have the selected value (in our case, the group ID that was just selected). This is enough to make the group selector work.&lt;br /&gt;
======Using &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;======&lt;br /&gt;
&amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; is an Angular directive that allows storing the value of a certain input or select in a JavaScript variable, and also the opposite way: tell the input or select which value to set. The main problem is that we cannot initialise a JavaScript variable from the template, so we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the PHP function we&#039;ll return the group that needs to be selected in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
        &#039;group&#039; =&amp;gt; $groupid,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we don&#039;t need to iterate over the groups array like in the plain HTML example. However, now we&#039;re returning the group id in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array. As it&#039;s explained in the [[#Using_otherdata|Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;]] section, this &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; is visible in the templates inside a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. So in the template we&#039;ll use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.group&amp;quot;&lt;br /&gt;
        (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: CONTENT_OTHERDATA.group})&amp;quot;&lt;br /&gt;
        interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Use the rich text editor===&lt;br /&gt;
The rich text editor included in the app requires a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt; to work. You can use the &amp;lt;code&amp;gt;FormBuilder&amp;lt;/code&amp;gt; library to create this control (or to create a whole &amp;lt;cide&amp;gt;FormGroup if you prefer).&lt;br /&gt;
&lt;br /&gt;
With the following JavaScript you&#039;ll be able to create a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.control = this.FormBuilder.control(this.CONTENT_OTHERDATA.rte);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we&#039;re using a value returned in &amp;lt;code&amp;gt;OTHERDATA&amp;lt;/code&amp;gt; as the initial value of the rich text editor, but you can use whatever you want.&lt;br /&gt;
&lt;br /&gt;
Then you need to pass this control to the rich text editor in your template:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;core-rich-text-editor item-content [control]=&amp;quot;control&amp;quot; placeholder=&amp;quot;Enter your text here&amp;quot; name=&amp;quot;rte_answer&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-rich-text-editor&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, there are several ways to send the value in the rich text editor to a Web Service to save it. This is one of the simplest options:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_webservice&amp;quot; [params]=&amp;quot;{rte: control.value}&amp;quot; ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re passing the value of the rich text editor as a parameter to our Web Service.&lt;br /&gt;
===Initialisation===&lt;br /&gt;
All handlers can specify an &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; method in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. This method is meant to return some JavaScript code that needs to be executed as soon as the plugin is retrieved.&lt;br /&gt;
&lt;br /&gt;
When the app retrieves all the handlers, the first thing it will do is call the &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt; Web Service with the initialisation method. This WS call will only receive the default arguments.&lt;br /&gt;
&lt;br /&gt;
The app will immediately execute the JavaScript code returned by this WS call. This JavaScript can be used to manually register your handlers in the delegates you want, without having to rely on the default handlers built based on the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; data.&lt;br /&gt;
&lt;br /&gt;
The templates returned by this method will be added to a &amp;lt;code&amp;gt;INIT_TEMPLATES&amp;lt;/code&amp;gt; variable that will be passed to all the JavaScript code of that handler. This means that the JavaScript returned by the initialisation method or the main method can access any of the templates HTML like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.INIT_TEMPLATES[&#039;main&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In this case, &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is the ID of the template we want to use.&lt;br /&gt;
&lt;br /&gt;
The same happens with the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the initialisation method, it is added to an &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt; variable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; field returned by this call will be used to determine if your handler is enabled or not. For example, if your handler is for the delegate &amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt; and you return a list of course ids in &amp;lt;code&amp;gt;restrict.courses&amp;lt;/code&amp;gt;, then your handler will only be enabled in the courses you returned. This only applies to the default handlers, if you register your own handler using the JavaScript code then you should check yourself if the handler is enabled.&lt;br /&gt;
&lt;br /&gt;
Finally, if you return an object in this initialisation JavaScript code, all the properties of that object will be passed to all the JavaScript code of that handler so you can use them when the code is run. For example, if your JavaScript code does something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    MyAddonClass: new MyAddonClass()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then, for the rest of JavaScript code of your handler (for example, the main method) you can use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.MyAddonClass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Link handlers=====&lt;br /&gt;
A link handler allows you to decide what to do when a link with a certain URL is clicked. This is useful, for example, to open your plugin page when a link to your plugin is clicked.&lt;br /&gt;
&lt;br /&gt;
After the 4.0 version, the Moodle app automatically creates two link handlers for module plugins, you don&#039;t need to create them in your plugin&#039;s Javascript code anymore:&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginanme/view.php?id=X&#039;&#039;. When this link is clicked, it will open your module in the app.&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginname/index.php?id=X&#039;&#039;. When this link is clicked, it will open a page in the app listing all the modules of your type inside a certain course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Link handlers have some advanced features that allow you to change how links behave under different conditions.&lt;br /&gt;
======Patterns======&lt;br /&gt;
You can define a Regular Expression pattern to match certain links. This will apply the handler only to links that match the pattern.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = RegExp(&#039;\/mod\/foo\/specialpage.php&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Priority======&lt;br /&gt;
Multiple link handlers may apply to a given link. You can define the order of precedence by setting the priority; the handler with the highest priority will be used.&lt;br /&gt;
&lt;br /&gt;
All default handlers have a priority of 0, so 1 or higher will override the default.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Multiple actions======&lt;br /&gt;
Once a link has been matched, the handler&#039;s &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method determines what the link should do. This method has access to the URL and its parameters.&lt;br /&gt;
&lt;br /&gt;
Different actions can be returned depending on different conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                action: function(siteId, navCtrl) {&lt;br /&gt;
                    // The actual behaviour of the link goes here.&lt;br /&gt;
                },&lt;br /&gt;
                sites: [&lt;br /&gt;
                    // ...&lt;br /&gt;
                ],&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                // ...&lt;br /&gt;
            },&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once handlers have been matched for a link, the actions will be fetched for all the matching handlers, in priorty order. The first valid action will be used to open the link.&lt;br /&gt;
&lt;br /&gt;
If your handler is matched with a link, but a condition assessed in the &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method means you want to revert to the next highest priorty handler, you can invalidate your action by settings its sites propety to an empty array.&lt;br /&gt;
======Complex example======&lt;br /&gt;
This will match all URLs containing &amp;lt;code&amp;gt;/mod/foo/&amp;lt;/code&amp;gt;, and force those with an id parameter that&#039;s not in the &amp;lt;code&amp;gt;supportedModFoos&amp;lt;/code&amp;gt; array to open in the user&#039;s browser, rather than the app.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const that = this;&lt;br /&gt;
const supportedModFoos = [...];&lt;br /&gt;
&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = new RegExp(&#039;\/mod\/foo\/&#039;);&lt;br /&gt;
        this.name = &#039;AddonModFooLinkHandler&#039;;&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {     &lt;br /&gt;
        const action = {&lt;br /&gt;
            action() {&lt;br /&gt;
                that.CoreUtilsProvider.openInBrowser(url);&lt;br /&gt;
            },&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (supportedModFoos.indexOf(parseInt(params.id)) !== -1) {&lt;br /&gt;
            action.sites = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return [action];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreContentLinksDelegate.registerHandler(new AddonModFooLinkHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Module prefetch handler=====&lt;br /&gt;
The &amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt; handler allows you to define a list of offline functions to prefetch a module. However, you might want to create your own prefetch handler to determine what needs to be downloaded. For example, you might need to chain WS calls (pass the result of a WS call to the next one), and this cannot be done using offline functions.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to create a prefetch handler using the initialisation JS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Create a class that extends from CoreCourseActivityPrefetchHandlerBase.&lt;br /&gt;
class AddonModCertificateModulePrefetchHandler extends CoreCourseActivityPrefetchHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.name = &#039;AddonModCertificateModulePrefetchHandler&#039;;&lt;br /&gt;
        this.modName = &#039;certificate&#039;;&lt;br /&gt;
&lt;br /&gt;
        // This must match the plugin identifier from db/mobile.php,&lt;br /&gt;
        // otherwise the download link in the context menu will not update correctly.&lt;br /&gt;
        this.component = &#039;mod_certificate&#039;;&lt;br /&gt;
        this.updatesNames = /^configuration$|^.*files$/;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Override the prefetch call.&lt;br /&gt;
    prefetch(module, courseId, single, dirPath) {&lt;br /&gt;
        return this.prefetchPackage(module, courseId, single, prefetchCertificate);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prefetchCertificate(module, courseId, single, siteId) {&lt;br /&gt;
    // Perform all the WS calls.&lt;br /&gt;
    // You can access most of the app providers using that.ClassName. E.g. that.CoreWSProvider.call().&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseModulePrefetchDelegate.registerHandler(new AddonModCertificateModulePrefetchHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
One relatively simple full example is where you have a function that needs to work offline, but it has an additional argument other than the standard ones. You can imagine for this an activity like the book module, where it has multiple pages for the same &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;. The app will not automatically work with this situation — it will call the offline function with the standard arguments only — so you won&#039;t be able to prefetch all the possible parameters.&lt;br /&gt;
&lt;br /&gt;
To deal with this, you need to implement a web service in your Moodle component that returns the list of possible extra arguments, and then you can call this web service and loop around doing the same thing the app does when it prefetches the offline functions. Here is an example from a third-party module (showing only the actual prefetch function, the rest of the code is as above) where there are multiple values of a custom &amp;lt;code&amp;gt;section&amp;lt;/code&amp;gt; parameter for the mobile function &amp;lt;code&amp;gt;mobile_document_view&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function prefetchOucontent(module, courseId, single, siteId) {&lt;br /&gt;
    var component = &#039;mod_oucontent&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Get the site, first.&lt;br /&gt;
    return that.CoreSitesProvider.getSite(siteId).then(function(site) {&lt;br /&gt;
        // Read the list of pages in this document using a web service.&lt;br /&gt;
        return site.read(&#039;mod_oucontent_get_page_list&#039;, {&#039;cmid&#039;: module.id}).then(function(response) {&lt;br /&gt;
            var promises = [];&lt;br /&gt;
&lt;br /&gt;
            // For each page, read and process the page - this is a copy of logic in the app at&lt;br /&gt;
            // siteplugins.ts (prefetchFunctions), but modified to add the custom argument.&lt;br /&gt;
            for(var i = 0; i &amp;lt; response.length; i++) {&lt;br /&gt;
                var args = {&lt;br /&gt;
                    courseid: courseId,&lt;br /&gt;
                    cmid: module.id,&lt;br /&gt;
                    userid: site.getUserId()&lt;br /&gt;
                };&lt;br /&gt;
                if (response[i] !== &#039;&#039;) {&lt;br /&gt;
                    args.section = response[i];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                promises.push(that.CoreSitePluginsProvider.getContent(&lt;br /&gt;
                        component, &#039;mobile_document_view&#039;, args).then(&lt;br /&gt;
                        function(result) {&lt;br /&gt;
                            var subPromises = [];&lt;br /&gt;
                            if (result.files &amp;amp;&amp;amp; result.files.length) {&lt;br /&gt;
                                subPromises.push(that.CoreFilepoolProvider.downloadOrPrefetchFiles(&lt;br /&gt;
                                        site.id, result.files, true, false, component, module.id));&lt;br /&gt;
                            }&lt;br /&gt;
                            return Promise.all(subPromises);&lt;br /&gt;
                        }));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(promises);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Single activity course format=====&lt;br /&gt;
In the following example, the value of &amp;lt;code&amp;gt;INIT_TEMPLATES[&#039;main&#039;]&amp;lt;/code&amp;gt; is:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;componentClass&amp;quot; [data]=&amp;quot;data&amp;quot;&amp;gt;&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This template is returned by the initialisation method. And this is the JavaScript code returned:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatComponent {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.data = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ngOnChanges(changes) {&lt;br /&gt;
        var self = this;&lt;br /&gt;
&lt;br /&gt;
        if (this.course &amp;amp;&amp;amp; this.sections &amp;amp;&amp;amp; this.sections.length) {&lt;br /&gt;
            var module = this.sections[0] &amp;amp;&amp;amp; this.sections[0].modules &amp;amp;&amp;amp; this.sections[0].modules[0];&lt;br /&gt;
            if (module &amp;amp;&amp;amp; !this.componentClass) {&lt;br /&gt;
                that.CoreCourseModuleDelegate.getMainComponent(that.Injector, this.course, module).then((component) =&amp;gt; {&lt;br /&gt;
                    self.componentClass = component || that.CoreCourseUnsupportedModuleComponent;&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.data.courseId = this.course.id;&lt;br /&gt;
            this.data.module = module;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    doRefresh(refresher, done) {&lt;br /&gt;
        return Promise.resolve(this.dynamicComponent.callComponentFunction(&amp;quot;doRefresh&amp;quot;, [refresher, done]));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatHandler {&lt;br /&gt;
    &lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.name = &#039;singleactivity&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    isEnabled() {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canViewAllSections() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseTitle(course, sections) {&lt;br /&gt;
        if (sections &amp;amp;&amp;amp; sections[0] &amp;amp;&amp;amp; sections[0].modules &amp;amp;&amp;amp; sections[0].modules[0]) {&lt;br /&gt;
            return sections[0].modules[0].name;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return course.fullname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displayEnableDownload() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displaySectionSelector() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseFormatComponent() {&lt;br /&gt;
        return that.CoreCompileProvider.instantiateDynamicComponent(that.INIT_TEMPLATES[&#039;main&#039;], AddonSingleActivityFormatComponent);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseFormatDelegate.registerHandler(new AddonSingleActivityFormatHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using the JavaScript API===&lt;br /&gt;
The JavaScript API is only supported by the delegates specified in the [[#Templates_downloaded_on_login_and_rendered_using_JS_data_2|Templates downloaded on login and rendered using JS data]] section. This API allows you to override any of the functions of the default handler.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; specified in a handler registered in the &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; will be called immediately after the initialisation method, and the JavaScript returned by this method will be run. If this JavaScript code returns an object with certain functions, these functions will override the ones in the default handler.&lt;br /&gt;
&lt;br /&gt;
For example, if the JavaScript returned by the method returns something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The the &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function of the default handler will be overridden by the returned &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
The default handler for &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; only has 2 functions: &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt;. In addition, the JavaScript code can return an extra function named &amp;lt;code&amp;gt;componentInit&amp;lt;/code&amp;gt; that will be executed when the component returned by &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; is initialised.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to support the text user profile field using this API:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
var result = {&lt;br /&gt;
    componentInit: function() {&lt;br /&gt;
        if (this.field &amp;amp;&amp;amp; this.edit &amp;amp;&amp;amp; this.form) {&lt;br /&gt;
            this.field.modelName = &#039;profile_field_&#039; + this.field.shortname;&lt;br /&gt;
&lt;br /&gt;
            if (this.field.param2) {&lt;br /&gt;
                this.field.maxlength = parseInt(this.field.param2, 10) || &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.field.inputType = that.CoreUtilsProvider.isTrueOrOne(this.field.param3) ? &#039;password&#039; : &#039;text&#039;;&lt;br /&gt;
&lt;br /&gt;
            var formData = {&lt;br /&gt;
                value: this.field.defaultdata,&lt;br /&gt;
                disabled: this.disabled,&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            this.form.addControl(this.field.modelName,&lt;br /&gt;
                that.FormBuilder.control(formData, this.field.required &amp;amp;&amp;amp; !this.field.locked ? that.Validators.required : null));&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        var name = &#039;profile_field_&#039; + field.shortname;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            type: &amp;quot;text&amp;quot;,&lt;br /&gt;
            name: name,&lt;br /&gt;
            value: that.CoreTextUtilsProvider.cleanTags(formValues[name]),&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Translate dynamic strings===&lt;br /&gt;
If you wish to have an element that displays a localised string based on value from your template you can doing something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_myactivity.&amp;lt;% status %&amp;gt;&#039; | translate }}&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This could save you from having to write something like when only one value should be displayed:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        &amp;lt;%#isedting%&amp;gt;{{ &#039;plugin.mod_myactivity.editing&#039; | translate }}&amp;lt;%/isediting%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isopen%&amp;gt;{{ &#039;plugin.mod_myactivity.open&#039; | translate }}&amp;lt;%/isopen%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isclosed%&amp;gt;{{ &#039;plugin.mod_myactivity.closed&#039; | translate }}&amp;lt;%/isclosed%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using strings with dates===&lt;br /&gt;
If you have a string that you wish to pass a formatted date, for example in the Moodle language file you have:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;strwithdate&#039;] = &#039;This string includes a date of {$a-&amp;gt;date} in the middle of it.&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can localise the string correctly in your template using something like the following:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{ &#039;plugin.mod_myactivity.strwithdate&#039; | translate: {$a: { date: &amp;lt;% timestamp %&amp;gt; * 1000 | coreFormatDate: &amp;quot;dffulldate&amp;quot; } } }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A Unix timestamp must be multiplied by 1000 as the Mobile App expects millisecond timestamps, whereas Unix timestamps are in seconds.&lt;br /&gt;
===Support push notification clicks===&lt;br /&gt;
If your plugin sends push notifications to the app, you might want to open a certain page in the app when the notification is clicked. There are several ways to achieve this.&lt;br /&gt;
&lt;br /&gt;
The easiest way is to include a &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; in your notification. When the notification is clicked, the app will try to open the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; will also be displayed in web. If you want to use a specific URL for the app, different than the one displayed in web, you can do so by returning a &amp;lt;code&amp;gt;customdata&amp;lt;/code&amp;gt; array that contains an &amp;lt;code&amp;gt;appurl&amp;lt;/code&amp;gt; property:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$notification-&amp;gt;customdata = [&lt;br /&gt;
    &#039;appurl&#039; =&amp;gt; $myurl-&amp;gt;out(),&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In both cases you will have to create a link handler to treat the URL. For more info on how to create the link handler, please see [[#Advanced_link_handler|how to create an advanced link handler]].&lt;br /&gt;
&lt;br /&gt;
If you want to do something that only happens when the notification is clicked, not when the link is clicked, you&#039;ll have to implement a push click handler yourself. The way to create it is similar to [[#Advanced_link_handler|creating an advanced link handler]], but you&#039;ll have to use &amp;lt;code&amp;gt;CorePushNotificationsDelegate&amp;lt;/code&amp;gt; and your handler will have to implement the properties and functions defined in the [https://github.com/moodlehq/moodleapp/blob/master/src/core/features/pushnotifications/services/push-delegate.ts#L27 CorePushNotificationsClickHandler] interface.&lt;br /&gt;
===Implement a module similar to mod_label===&lt;br /&gt;
In Moodle 3.8 or higher, if your plugin doesn&#039;t support &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt; and you don&#039;t specify a &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; then the module will only display the module description in the course page and it won&#039;t be clickable in the app, just like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt;. You can decide if you want the module icon to be displayed or not (if you don&#039;t want it to be displayed, then don&#039;t define it in &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
However, if your plugin needs to work in previous versions of Moodle or you want to display something different than the description then you need a different approach.&lt;br /&gt;
&lt;br /&gt;
If your plugin wants to render something in the course page instead of just the module name and description you should specify the &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. The template returned by this method will be rendered in the course page. Please notice the HTML returned should not contain directives or components, only plain HTML.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want your module to be clickable then you just need to remove &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. With these 2 changes you can have a module that behaves like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt; in the app.&lt;br /&gt;
===Use Ionic navigation lifecycle functions===&lt;br /&gt;
Ionic let pages define some functions that will be called when certain navigation lifecycle events happen. For more info about these functions, see [https://ionicframework.com/docs/api/router-outlet Ionic&#039;s documentation].&lt;br /&gt;
&lt;br /&gt;
You can define these functions in your plugin javascript:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.ionViewWillLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In addition to that, you can also implement &amp;lt;code&amp;gt;canLeave&amp;lt;/code&amp;gt; to use Angular route guards:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.canLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So for example you can make your plugin ask for confirmation if the user tries to leave the page when he has some unsaved data.&lt;br /&gt;
===Module plugins: dynamically determine if a feature is supported===&lt;br /&gt;
In Moodle you can specify if your plugin supports a certain feature, like &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. If your plugin will always support or not a certain feature, then you can use the &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; to specify it ([[#Options_only_for_CoreCourseModuleDelegate|see more documentation about this]]). But if you need to calculate it dynamically then you will have to create a function to calculate it. &lt;br /&gt;
&lt;br /&gt;
This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section above). The JavaScript returned by your initialisation method will need to define a function named &amp;lt;code&amp;gt;supportsFeature&amp;lt;/code&amp;gt; that will receive the name of the feature:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    supportsFeature: function(featureName) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;.&lt;br /&gt;
== Testing ==&lt;br /&gt;
You can also write automated tests for your plugin using Behat, you can read more about it on the [[Acceptance testing for the Moodle App]] page.&lt;br /&gt;
== Upgrading plugins from an older version ==&lt;br /&gt;
If you added mobile support to your plugin for the Ionic 3 version of the app (previous to the 3.9.5 release), you will probably need to make some changes to make it compatible with Ionic 5.&lt;br /&gt;
&lt;br /&gt;
Learn more at the [[Moodle App Plugins Upgrade Guide]].&lt;br /&gt;
==Troubleshooting==&lt;br /&gt;
=== Invalid response received ===&lt;br /&gt;
You might receive this error when using the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive or similar. By default, the app expects all Web Service calls to return an object, if your Web Service returns another type (string, boolean, etc.) then you need to specify it using the &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; attribute of the directive. For example, if your WS returns a boolean value, then you should specify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{typeExpected: &#039;boolean&#039;}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In a similar way, if your Web Service returns &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; you need to tell the app not to expect any result using &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{responseExpected: false}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS ===&lt;br /&gt;
Some directives allow you to specify a form id or name to send the data from the form to a certain WS. These directives look for HTML inputs to retrieve the data to send. However, &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; don&#039;t use HTML inputs, they simulate them, so the directive isn&#039;t going to find their data and so it won&#039;t be sent to the Web Service.&lt;br /&gt;
&lt;br /&gt;
There are 2 workarounds to fix this problem.&lt;br /&gt;
==== Sending the data manually ====&lt;br /&gt;
The first solution is to send the missing params manually using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; property. We will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input value in a variable, and this variable will be passed to the parameters. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a template like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group name=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, responses: responses}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Basically, you need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to the affected element (in this case, the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt;). You can put whatever name you want as the value, we used &amp;quot;responses&amp;quot;. With this, every time the user selects a radio button the value will be stored in a variable called &amp;quot;responses&amp;quot;. Then, in the button we are passing this variable to the parameters of the Web Service.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute has priority over &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;, so if you have an input with &amp;lt;code&amp;gt;name=&amp;quot;responses&amp;quot;&amp;lt;/code&amp;gt; it will override what you&#039;re manually passing to &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;.&lt;br /&gt;
==== Using a hidden input ====&lt;br /&gt;
Since the directive is looking for HTML inputs, you need to add one with the value to send to the server. You can use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to synchronise your radio/checkbox/select with the new hidden input. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a radio button like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot; [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;hidden&amp;quot; [ngModel]=&amp;quot;responses&amp;quot; name=&amp;quot;responses&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we&#039;re using a variable called &amp;quot;responses&amp;quot; to synchronise the data between the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt; and the hidden input. You can use whatever name you want.&lt;br /&gt;
=== I can&#039;t return an object or array in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; ===&lt;br /&gt;
If you try to return an object or an array in any field inside &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, the Web Service call will fail with the following error:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Scalar type expected, array or object received&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Each field in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; must be a string, number or boolean; it cannot be an object or array. To make it work, you need to encode your object or array into a JSON string:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($data)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app will automatically parse this JSON and convert it back into an array or object.&lt;br /&gt;
==Examples==&lt;br /&gt;
===Accepting dynamic names in a Web Service===&lt;br /&gt;
We want to display a form where the names of the fields are dynamic, like it happens in quiz. This data will be sent to a new Web Service that we have created.&lt;br /&gt;
&lt;br /&gt;
The first issue we find is that the Web Service needs to define the names of the parameters received, but in this case they&#039;re dynamic. The solution is to accept an array of objects with name and value. So in the &amp;lt;code&amp;gt;_parameters()&amp;lt;/code&amp;gt; function of our new Web Service, we will add this parameter:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;data&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
     new external_single_structure(&lt;br /&gt;
        [&lt;br /&gt;
            &#039;name&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data name&#039;),&lt;br /&gt;
            &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data value&#039;),&lt;br /&gt;
        ]&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;The data to be saved&#039;, VALUE_DEFAULT, []&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now we need to adapt our form to send the data as the Web Service requires it. In our template, we have a button with the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive that will send the form data to our Web Service. To make this work we will have to pass the parameters manually, without using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute, because we need to format the data before it is sent.&lt;br /&gt;
&lt;br /&gt;
Since we will send the parameters manually and we want it all to be sent in the same array, we will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input data into a variable that we&#039;ll call &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;, but you can use the name you want. This variable will be an object that will hold the input data with the format &amp;quot;name-&amp;gt;value&amp;quot;. For example, if I have an input with name &amp;quot;a1&amp;quot; and value &amp;quot;My answer&amp;quot;, the data object will be:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{a1: &#039;My answer&#039;}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So we need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to all the inputs whose values need to be sent to the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; WS param. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-input name=&amp;quot;&amp;lt;% name %&amp;gt;&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.data[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re using &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; to store the data. We do it like this because we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the form, setting the values the user has already stored. If you don&#039;t need to initialise the form, then you can use the &amp;lt;code&amp;gt;dataObject&amp;lt;/code&amp;gt; variable, an empty object that the mobile app creates for you:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[(ngModel)]=&amp;quot;dataObject[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app has a function that allows you to convert this data object into an array like the one the WS expects: &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt;. So in our button we&#039;ll use this function to format the data before it&#039;s sent:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_ws_name&amp;quot;&lt;br /&gt;
    [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, data: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.data, &#039;name&#039;, &#039;value&#039;)}&amp;quot;&lt;br /&gt;
    successMessage&lt;br /&gt;
    refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see in the example above, we&#039;re specifying that the keys of the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object need to be stored in a property called &amp;quot;name&amp;quot;, and the values need to be stored in a property called &amp;quot;value&amp;quot;. If your Web Service expects different names you need to change the parameters of the &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
If you open your plugin now in the app it will display an error in the JavaScript console. The reason is that the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; variable doesn&#039;t exist inside &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. As it is explained in previous sections, &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; holds the data that you return in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; for your method. We&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the values to be displayed in the form.&lt;br /&gt;
&lt;br /&gt;
If the user hasn&#039;t answered the form yet, we can initialise the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object as an empty object. Please remember that we cannot return arrays or objects in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, so we&#039;ll return a JSON string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; &#039;{}&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
With the code above, the form will always be empty when the user opens it. But now we want to check if the user has already answered the form and fill the form with the previous values. We will do it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$userdata = get_user_responses(); // It will held the data in a format name-&amp;gt;value. Example: [&#039;a1&#039; =&amp;gt; &#039;My value&#039;].&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($userdata)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now the user will be able to see previous values when the form is opened, and clicking the button will send the data to our Web Service in array format.&lt;br /&gt;
==Moodle plugins with mobile support==&lt;br /&gt;
* Group choice: [https://moodle.org/plugins/mod_choicegroup Moodle plugins directory entry] and [https://github.com/ndunand/moodle-mod_choicegroup code in github].&lt;br /&gt;
* Custom certificate: [https://moodle.org/plugins/mod_customcert Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_customcert code in github].&lt;br /&gt;
* Gapfill question type: [https://moodle.org/plugins/qtype_gapfill Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_gapfill in github].&lt;br /&gt;
* Wordselect question type: [https://moodle.org/plugins/qtype_wordselect Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_wordselect in github].&lt;br /&gt;
* RegExp question type: [https://moodle.org/plugins/qtype_regexp Moodle plugins directory entry] and [https://github.com/rezeau/moodle-qtype_regexp in github].&lt;br /&gt;
* Certificate: [https://moodle.org/plugins/mod_certificate Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_certificate in github].&lt;br /&gt;
* Attendance [https://moodle.org/plugins/mod_attendance Moodle plugins directory entry] and [https://github.com/danmarsden/moodle-mod_attendance in github].&lt;br /&gt;
* ForumNG (unfinished support) [https://moodle.org/plugins/mod_forumng Moodle plugins directory entry] and [https://github.com/moodleou/moodle-mod_forumng in github].&lt;br /&gt;
* News block [https://github.com/moodleou/moodle-block_news in github].&lt;br /&gt;
* H5P activity module [https://moodle.org/plugins/mod_hvp Moodle plugins directory entry] and [https://github.com/h5p/h5p-moodle-plugin in github].&lt;br /&gt;
See the complete list in [https://moodle.org/plugins/browse.php?list=award&amp;amp;id=6 the plugins database] (it may contain some outdated plugins).&lt;br /&gt;
=== Mobile app support award ===&lt;br /&gt;
If you want your plugin to be awarded in the plugins directory and marked as supporting the mobile app, please feel encouraged to contact us via email at [mailto:mobile@moodle.com mobile@moodle.com].&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to include a link to your plugin page and the location of its code repository.&lt;br /&gt;
&lt;br /&gt;
See [https://moodle.org/plugins/?q=award:mobile-app the list of awarded plugins] in the plugins directory.&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Moodle App Ionic 5]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62053</id>
		<title>Moodle App Plugins Development Guide</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=62053"/>
		<updated>2022-04-25T07:58:05Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: /* Custom core components and directives */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle App (Ionic 5)}}&lt;br /&gt;
If you want to add mobile support to your Moodle plugin, you can achieve it by extending different areas of the app using &#039;&#039;just PHP server side code&#039;&#039; and providing templates written with [https://ionicframework.com/docs/components Ionic] and custom components.&lt;br /&gt;
&lt;br /&gt;
You will have to:&lt;br /&gt;
# Create a &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file in your plugin. In this file, you will be able to indicate which areas of the app you want to extend. For example, adding a new option in the main menu, implementing support for a new activity module, including a new option in the course menu, including a new option in the user profile, etc. All the areas supported are described further in this document.&lt;br /&gt;
# Create new functions in a reserved namespace that will return the content of the new options. The content should be returned rendered as html. This html should use Ionic components so that it looks native, but it can be generated using mustache templates.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let’s clarify some points:&lt;br /&gt;
* You don’t need to create new Web Service functions (although you will be able to use them for advanced features). You just need plain php functions that will be placed in a reserved namespace.&lt;br /&gt;
* Those functions will be exported via the Web Service function &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt;.&lt;br /&gt;
* As arguments of your functions you will always receive the &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, some relevant details of the app (like the app version or the current language in the app), and some specific data depending on the type of plugin (&amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
* The mobile app also implements a list of custom Ionic components and directives that provide dynamic behaviour; like indicating that you are linking a file that can be downloaded, allowing a transition to new pages into the app calling a specific function in the server, submitting form data to the server, etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Getting started==&lt;br /&gt;
If you only want to write a plugin, it is not necessary that you set up your environment to work with the Moodle App. In fact, you don&#039;t even need to compile it. You can just [[Using the Moodle App in a browser|use a Chromium-based browser]] to add mobile support to your plugins!&lt;br /&gt;
&lt;br /&gt;
You can use the app from one of the hosted versions on [https://master.apps.moodledemo.net master.apps.moodledemo.net] (the latest stable version) and [https://integration.apps.moodledemo.net integration.apps.moodledemo.net] (the latest development version). If you need any specific environment (hosted versions are deployed with a &#039;&#039;production&#039;&#039; environment), you can also use [[Moodle App Docker Images|Docker images]]. And if you need to test your plugin in a native device, you can always use [https://download.moodle.org/mobile Moodle HQ&#039;s application].&lt;br /&gt;
&lt;br /&gt;
This should suffice for developing plugins. However, if you are working on advanced functionality and you need to run the application from the source code, you can find more information in the [[Moodle App Development Guide]].&lt;br /&gt;
===Development workflow===&lt;br /&gt;
Before getting into the specifics of your plugin, we recommend that you start adding a simple &amp;quot;Hello World&amp;quot; button in the app to see that everything works properly.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s say your plugin is called &amp;lt;code&amp;gt;local_hello&amp;lt;/code&amp;gt;, you can start by adding the following files:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;local_hello&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [&lt;br /&gt;
            &#039;hello&#039; =&amp;gt; [&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreMainMenuDelegate&#039;,&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;view_hello&#039;,&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;title&#039; =&amp;gt; &#039;hello&#039;,&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; &#039;earth&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [&lt;br /&gt;
            [&#039;hello&#039;, &#039;local_hello&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace local_hello\output;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    public static function view_hello() {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; &#039;&amp;lt;h1 class=&amp;quot;text-center&amp;quot;&amp;gt;{{ &amp;quot;plugin.local_hello.hello&amp;quot; | translate }}&amp;lt;/h1&amp;gt;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;lang/en/local_hello.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$string[&#039;hello&#039;] = &#039;Hello World&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once you&#039;ve done that, try logging into your site in the app and you should see a new button in the main menu or more menu (depending on the device) saying &amp;quot;Hello World&amp;quot;. If you press this button, you should see a page saying &amp;quot;Hello World!&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Congratualtions, you have written your first Moodle plugin with moodle support!&lt;br /&gt;
&lt;br /&gt;
You can read the rest of this page to learn more about mobile plugins and start working on your plugin. Here&#039;s some things to keep in mind:&lt;br /&gt;
* If you change the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file, you will have to refresh the browser. And remember to [https://developer.chrome.com/docs/devtools/network/reference/#disable-cache disable the network cache].&lt;br /&gt;
* If you change an existing template or function, you won’t have to refresh the browser. In most cases, doing a PTR (Pull To Refresh) in the page that displays the template will suffice.&lt;br /&gt;
* If any of these doesn&#039;t show your changes, you may need to [https://docs.moodle.org/311/en/Developer_tools#Purge_all_caches purge all caches] to avoid problems with the auto-loading cache.&lt;br /&gt;
* Ultimately, if that doesn&#039;t work either, you may have to log out from the site and log in again. If any changes affect plugin installation, you may also need to increase the version in your plugin&#039;s &amp;lt;code&amp;gt;version.php&amp;lt;/code&amp;gt; file and upgrade it in the site.&lt;br /&gt;
==Types of plugins==&lt;br /&gt;
There are 3 types of plugins:&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
[[File:Templates_downloaded_when_requested.png|thumb]]&lt;br /&gt;
With this type of plugin, the template of your plugin will be generated and downloaded when the user opens the plugin in the app. This means that your function will receive some context parameters. For example, if you&#039;re developing a course module plugin you will receive the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; (course module ID). You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
[[File:Templates_downloaded_on_login.png|thumb]]&lt;br /&gt;
With this type of plugin, the template for your plugin will be downloaded when the user logs in into the app and will be stored in the device. This means that your function will not receive any context parameters, and you need to return a generic template that will be built with JS data like the ones in the Moodle App. When the user opens a page that includes your plugin, your template will receive the required JS data and your template will be rendered. You can see the list of delegates that support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
You can always implement the whole plugin yourself using JavaScript instead of using our API. In fact, this is required if you want to implement some features like capturing links in the Mobile app. You can see the list of delegates that only support this type of plugin in the [[#Delegates|Delegates]] section.&lt;br /&gt;
==Step by step example==&lt;br /&gt;
In this example, we are going to update an existing plugin, the [https://github.com/mdjnelson/moodle-mod_certificate Certificate activity module], that previously used a [[Moodle Mobile 2 (Ionic 1) Remote add-ons|Remote add-on]] (a legacy approach to implement mobile plugins).&lt;br /&gt;
&lt;br /&gt;
This is a simple activity module that displays the certificate issued for the current user along with the list of the dates of previously issued certificates. It also stores in the course log that the user viewed a certificate. This module also works offline: when the user downloads the course or activity, the data is pre-fetched and can be viewed offline.&lt;br /&gt;
===Step 1. Update the &amp;lt;code&amp;gt;db/mobile.php&amp;lt;/code&amp;gt; file===&lt;br /&gt;
In this case, we are updating an existing file. For new plugins, you should create this new file.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;mod_certificate&#039; =&amp;gt; [ // Plugin identifier&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;coursecertificate&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;displaydata&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;icon&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/mod/certificate/pix/icon.gif&#039;,&lt;br /&gt;
                    &#039;class&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
                ],&lt;br /&gt;
       &lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseModuleDelegate&#039;, // Delegate (where to display the link to the plugin)&lt;br /&gt;
                &#039;method&#039; =&amp;gt; &#039;mobile_course_view&#039;, // Main function in \mod_certificate\output\mobile&lt;br /&gt;
                &#039;offlinefunctions&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;mobile_course_view&#039; =&amp;gt; [],&lt;br /&gt;
                    &#039;mobile_issues_view&#039; =&amp;gt; [],&lt;br /&gt;
                ], // Function that needs to be downloaded for offline.&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;lang&#039; =&amp;gt; [ // Language strings that are used in all the handlers.&lt;br /&gt;
            [&#039;pluginname&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;summaryofattempts&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;getcertificate&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;requiredtimenotmet&#039;, &#039;certificate&#039;],&lt;br /&gt;
            [&#039;viewcertificateviews&#039;, &#039;certificate&#039;],&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Plugin identifier:&lt;br /&gt;
: A unique name for the plugin, it can be anything (there’s no need to match the module name).&lt;br /&gt;
&lt;br /&gt;
;Handlers (Different places where the plugin will display content):&lt;br /&gt;
: A plugin can be displayed in different views in the app. Each view should have a unique name inside the plugin scope (alphanumeric).&lt;br /&gt;
&lt;br /&gt;
; Display data:&lt;br /&gt;
: This is only needed for certain types of plugins. Also, depending on the type of delegate it may require additional (or less fields). In this case, we are indicating the module icon.&lt;br /&gt;
&lt;br /&gt;
; Delegate&lt;br /&gt;
: Where to display the link to the plugin, see the [[#Delegates|Delegates]] section for all the possible options.&lt;br /&gt;
&lt;br /&gt;
; Method:&lt;br /&gt;
: This is the method in the Moodle &amp;lt;code&amp;gt;\{component-name}\output\mobile&amp;lt;/code&amp;gt; class to be executed the first time the user clicks in the new option displayed in the app.&lt;br /&gt;
&lt;br /&gt;
; Offlinefunctions&lt;br /&gt;
: This is the list of functions that need to be called and stored when the user downloads a course for offline usage. Please note that you can add functions here that are not even listed in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. &lt;br /&gt;
: In our example, downloading for offline access will mean that we&#039;ll execute the functions for getting the certificate and issued certificates passing as parameters the current &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; (and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt; when we are using the mod or course delegate). If we have the result of those functions stored in the app, we&#039;ll be able to display the certificate information even if the user is offline.&lt;br /&gt;
: Offline functions will be mostly used to display information for final users, any further interaction with the view won’t be supported offline (for example, trying to send information when the user is offline).&lt;br /&gt;
: You can indicate here other Web Services functions, indicating the parameters that they might need from a defined subset (currently &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Prefetching the module will also download all the files returned by the methods in these offline functions (in the &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; array).&lt;br /&gt;
: Note that if your functions use additional custom parameters (for example, if you implement multiple pages within a module&#039;s view function by using a &amp;lt;code&amp;gt;page&amp;lt;/code&amp;gt; parameter in addition to the usual &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;) then the app will not know which additional parameters to supply. In this case, do not list the function in &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt;; instead, you will need to manually implement a [[#Module_prefetch_handler|module prefetch handler]].&lt;br /&gt;
&lt;br /&gt;
;Lang:&lt;br /&gt;
: The language pack string ids used in the plugin by all the handlers. Normally these will be strings from your own plugin, however, you can list any strings you need here, like &amp;lt;code&amp;gt;[&#039;cancel&#039;, &#039;moodle&#039;]&amp;lt;/code&amp;gt;. If you do this, be warned that in the app you will then need to refer to that string as &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.myplugin.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt; (not &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;{{ &#039;plugin.moodle.cancel&#039; | translate }}&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;).&lt;br /&gt;
: Please only include the strings you actually need. The Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform, and this will then be cached, so listing too many strings is very wasteful.&lt;br /&gt;
There are additional attributes supported by the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; list, you can find about them in the [[#Mobile.php_supported_options|Mobile.php supported options]] section.&lt;br /&gt;
===Step 2. Creating the main function===&lt;br /&gt;
The main function displays the current issued certificate (or several warnings if it’s not possible to issue a certificate). It also displays a link to view the dates of previously issued certificates.&lt;br /&gt;
&lt;br /&gt;
All the functions must be created in the plugin or subsystem &amp;lt;code&amp;gt;classes/output&amp;lt;/code&amp;gt; directory, the name of the class must be &amp;lt;code&amp;gt;mobile&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
For this example, the namespace name will be &amp;lt;code&amp;gt;mod_certificate\output&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
namespace mod_certificate\output;&lt;br /&gt;
&lt;br /&gt;
use context_module;&lt;br /&gt;
use mod_certificate_external;&lt;br /&gt;
&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Returns the certificate course view for the mobile app.&lt;br /&gt;
     *&lt;br /&gt;
     * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
     *&lt;br /&gt;
     * @return array       HTML, JS and other data.&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
        $args = (object) $args;&lt;br /&gt;
        $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
        // Capabilities check.&lt;br /&gt;
        require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
        $context = \context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
        require_capability(&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
        if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
            require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
        }&lt;br /&gt;
        $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
        // Get certificates from external (taking care of exceptions).&lt;br /&gt;
        try {&lt;br /&gt;
            $issued = \mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
            $certificates = \mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
            $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
        } catch (Exception $e) {&lt;br /&gt;
            $issues = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Set timemodified for each certificate.&lt;br /&gt;
        foreach ($issues as $issue) {&lt;br /&gt;
            if (empty($issue-&amp;gt;timemodified)) {&lt;br /&gt;
                $issue-&amp;gt;timemodified = $issue-&amp;gt;timecreated;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $showget = true;&lt;br /&gt;
        if ($certificate-&amp;gt;requiredtime &amp;amp;&amp;amp; !has_capability(&#039;mod/certificate:manage&#039;, $context)) {&lt;br /&gt;
            if (certificate_get_course_time($certificate-&amp;gt;course) &amp;lt; ($certificate-&amp;gt;requiredtime * 60)) {&lt;br /&gt;
                $showget = false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $certificate-&amp;gt;name = format_string($certificate-&amp;gt;name);&lt;br /&gt;
        [$certificate-&amp;gt;intro, $certificate-&amp;gt;introformat] =&lt;br /&gt;
                external_format_text($certificate-&amp;gt;intro, $certificate-&amp;gt;introformat, $context-&amp;gt;id, &#039;mod_certificate&#039;, &#039;intro&#039;);&lt;br /&gt;
        $data = [&lt;br /&gt;
            &#039;certificate&#039; =&amp;gt; $certificate,&lt;br /&gt;
            &#039;showget&#039; =&amp;gt; $showget &amp;amp;&amp;amp; count($issues) &amp;gt; 0,&lt;br /&gt;
            &#039;issues&#039; =&amp;gt; $issues,&lt;br /&gt;
            &#039;issue&#039; =&amp;gt; $issues[0],&lt;br /&gt;
            &#039;numissues&#039; =&amp;gt; count($issues),&lt;br /&gt;
            &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
            &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
        ];&lt;br /&gt;
&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
                [&lt;br /&gt;
                    &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                    &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
                ],&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
            &#039;files&#039; =&amp;gt; $issues,&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
;Function declaration: &lt;br /&gt;
: The function name is the same as the one used in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file (&amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; field). There is only one argument, &amp;lt;code&amp;gt;$args&amp;lt;/code&amp;gt;, which is an array containing all the information sent by the mobile app (the &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversionname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appversioncode&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;applang&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;appcustomurlscheme&amp;lt;/code&amp;gt;, ...).&lt;br /&gt;
&lt;br /&gt;
; Function implementation:&lt;br /&gt;
: In the first part of the function, we check permissions and capabilities (like a &amp;lt;code&amp;gt;view.php&amp;lt;/code&amp;gt; script would do normally). Then we retrieve the certificate information that’s necessary to display the template.&lt;br /&gt;
&lt;br /&gt;
; Function return:&lt;br /&gt;
* &amp;lt;code&amp;gt;templates&amp;lt;/code&amp;gt; — The rendered template (notice that we could return more than one template, but we usually would only need one). By default the app will always render the first template received, the rest of the templates can be used if the plugin defines some JavaScript code.&lt;br /&gt;
* &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; — Empty, because we don’t need any in this case.&lt;br /&gt;
* &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; — Empty as well, because we don’t need any additional data to be used by directives or components in the template. This field will be published as an object supporting 2-way data-binding in the template.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; — A list of files that the app should be able to download (for offline usage mostly).&lt;br /&gt;
===Step 3. Creating the template for the main function===&lt;br /&gt;
This is the most important part of your plugin because it contains the code that will be rendered on the mobile app.&lt;br /&gt;
&lt;br /&gt;
In this template we’ll be using Ionic, together with directives and components specific to the Moodle App.&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;ion-&amp;lt;/code&amp;gt; are ionic components. Most of the time, the component name is self-explanatory but you may refer to a detailed guide here: https://ionicframework.com/docs/components/&lt;br /&gt;
&lt;br /&gt;
All the HTML elements starting with &amp;lt;code&amp;gt;core-&amp;lt;/code&amp;gt; are custom components of the Moodle App.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_page.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;core-course-module-description description=&amp;quot;&amp;lt;% certificate.intro %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-course-module-description&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;ion-list-header&amp;gt;&lt;br /&gt;
            &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.summaryofattempts&#039; | translate }}&amp;lt;/p&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-list-header&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-new-content title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; &lt;br /&gt;
                        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
                        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
                    {{ &#039;plugin.mod_certificate.viewcertificateviews&#039; | translate: {$a: &amp;lt;% numissues %&amp;gt;} }}&lt;br /&gt;
                &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%#showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot;&lt;br /&gt;
                        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
                        [files]=&amp;quot;[{&lt;br /&gt;
                            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
                            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
                            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
                        }]&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot; item-start&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
                &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;%^showget%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;p&amp;gt;{{ &#039;plugin.mod_certificate.requiredtimenotmet&#039; | translate }}&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/showget%&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;!-- Call log WS when the template is loaded. --&amp;gt;&lt;br /&gt;
        &amp;lt;span core-site-plugins-call-ws-on-load name=&amp;quot;mod_certificate_view_certificate&amp;quot;&lt;br /&gt;
                [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/span&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache).&lt;br /&gt;
&lt;br /&gt;
Then we display the module description using &amp;lt;code&amp;gt;core-course-module-description&amp;lt;/code&amp;gt;, which is a component used to include the course module description.&lt;br /&gt;
&lt;br /&gt;
For displaying the certificate information we create a list of elements, adding a header on top.&lt;br /&gt;
&lt;br /&gt;
The following line using the &amp;lt;code&amp;gt;translate&amp;lt;/code&amp;gt; filter indicates that the app will translate the &amp;lt;code&amp;gt;summaryofattempts&amp;lt;/code&amp;gt; string id (here we could’ve used mustache translation but it is usually better to delegate the strings translations to the app). The string id has the following format: &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
plugin.{plugin-identifier}.{string-id}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Where &amp;lt;code&amp;gt;{plugin-identifier}&amp;lt;/code&amp;gt; is taken from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;{string-id}&amp;lt;/code&amp;gt; must be indicated in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; field in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Then, we display a button to transition to another page if there are certificates issued. The attribute (directive) &amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt; indicates that if the user clicks the button, we need to call the &amp;lt;code&amp;gt;mobile_issues_view&amp;lt;/code&amp;gt; function in the &amp;lt;code&amp;gt;mod_certificate&amp;lt;/code&amp;gt; component; passing as arguments the &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;. The content returned by this function will be displayed in a new page (read the following section to see the code of this new page).&lt;br /&gt;
&lt;br /&gt;
Just after this button, we display another one but this time for downloading an issued certificate. The &amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt; directive indicates that clicking this button is for downloading the whole activity and opening the main file. This means that, when the user clicks this button, the whole certificate activity will be available offline.&lt;br /&gt;
&lt;br /&gt;
Finally, just before the &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt; is closed, we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt; directive to indicate that once the page is loaded, we need to call a Web Service function in the server, in this case we are calling the &amp;lt;code&amp;gt;mod_certificate_view_certificate&amp;lt;/code&amp;gt; that will log that the user viewed this page.&lt;br /&gt;
&lt;br /&gt;
As you can see, no JavaScript was necessary at all. We used plain HTML elements and attributes that did all the complex dynamic logic (like calling a Web Service) behind the scenes.&lt;br /&gt;
===Step 4. Adding an additional page===&lt;br /&gt;
Add the following method to &amp;lt;code&amp;gt;mod/certificate/classes/output/mobile.php&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Returns the certificate issues view for the mobile app.&lt;br /&gt;
 * @param  array $args Arguments from tool_mobile_get_content WS.&lt;br /&gt;
 *&lt;br /&gt;
 * @return array       HTML, JS and other data.&lt;br /&gt;
 */&lt;br /&gt;
public static function mobile_issues_view($args) {&lt;br /&gt;
    global $OUTPUT, $USER, $DB;&lt;br /&gt;
&lt;br /&gt;
    $args = (object) $args;&lt;br /&gt;
    $cm = get_coursemodule_from_id(&#039;certificate&#039;, $args-&amp;gt;cmid);&lt;br /&gt;
&lt;br /&gt;
    // Capabilities check.&lt;br /&gt;
    require_login($args-&amp;gt;courseid, false, $cm, true, true);&lt;br /&gt;
&lt;br /&gt;
    $context = context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
    require_capability (&#039;mod/certificate:view&#039;, $context);&lt;br /&gt;
    if ($args-&amp;gt;userid != $USER-&amp;gt;id) {&lt;br /&gt;
        require_capability(&#039;mod/certificate:manage&#039;, $context);&lt;br /&gt;
    }&lt;br /&gt;
    $certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, [&#039;id&#039; =&amp;gt; $cm-&amp;gt;instance]);&lt;br /&gt;
&lt;br /&gt;
    // Get certificates from external (taking care of exceptions).&lt;br /&gt;
    try {&lt;br /&gt;
        $issued = mod_certificate_external::issue_certificate($cm-&amp;gt;instance);&lt;br /&gt;
        $certificates = mod_certificate_external::get_issued_certificates($cm-&amp;gt;instance);&lt;br /&gt;
        $issues = array_values($certificates[&#039;issues&#039;]); // Make it mustache compatible.&lt;br /&gt;
    } catch (Exception $e) {&lt;br /&gt;
        $issues = [];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $data = [&#039;issues&#039; =&amp;gt; $issues];&lt;br /&gt;
&lt;br /&gt;
    return [&lt;br /&gt;
        &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
            [&lt;br /&gt;
                &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
                &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_issues&#039;, $data),&lt;br /&gt;
            ],&lt;br /&gt;
        ],&lt;br /&gt;
        &#039;javascript&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
        &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    ];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This method for the new page was added just after &amp;lt;code&amp;gt;mobile_course_view&amp;lt;/code&amp;gt;, the code is quite similar: checks the capabilities, retrieves the information required for the template, and returns the template rendered.&lt;br /&gt;
&lt;br /&gt;
The code of the mustache template is also very simple.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/templates/mobile_view_issues.mustache&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
    &amp;lt;ion-list&amp;gt;&lt;br /&gt;
        &amp;lt;%#issues%&amp;gt;&lt;br /&gt;
            &amp;lt;ion-item&amp;gt;&lt;br /&gt;
                &amp;lt;p class=&amp;quot;item-heading&amp;quot;&amp;gt;{{ &amp;lt;%timecreated%&amp;gt; | coreToLocaleString }}&amp;lt;/p&amp;gt;&lt;br /&gt;
                &amp;lt;p&amp;gt;&amp;lt;%grade%&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
            &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;%/issues%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As we did in the previous template, in the first line of the template we switch delimiters to avoid conflicting with Ionic delimiters (that are curly brackets like mustache). &lt;br /&gt;
&lt;br /&gt;
Here we are creating an Ionic list that will display a new item in the list per each issued certificated.&lt;br /&gt;
&lt;br /&gt;
For the issued certificated we’ll display the time when it was created (using the app filter &amp;lt;code&amp;gt;coreToLocaleString&amp;lt;/code&amp;gt;). We are also displaying the grade displayed in the certificate (if any).&lt;br /&gt;
===Step 5. Plugin webservices, if included===&lt;br /&gt;
If your plugin uses its own web services, they will also need to be enabled for mobile access in your &amp;lt;code&amp;gt;db/services.php&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
The following line should be included in each webservice definition:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;services&#039; =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;mod/certificate/db/services.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
$functions = [&lt;br /&gt;
    &#039;mod_certificate_get_certificates_by_courses&#039; =&amp;gt; [&lt;br /&gt;
        &#039;classname&#039;     =&amp;gt; &#039;mod_certificate_external&#039;,&lt;br /&gt;
        &#039;methodname&#039;    =&amp;gt; &#039;get_certificates_by_courses&#039;,&lt;br /&gt;
        &#039;description&#039;   =&amp;gt; &#039;Returns a list of certificate instances...&#039;,&lt;br /&gt;
        &#039;type&#039;          =&amp;gt; &#039;read&#039;,&lt;br /&gt;
        &#039;capabilities&#039;  =&amp;gt; &#039;mod/certificate:view&#039;,&lt;br /&gt;
        &#039;services&#039;      =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Mobile.php supported options==&lt;br /&gt;
In the previous section, we learned about some of the existing options for handlers configuration. This is the full list of supported options.&lt;br /&gt;
===Common options===&lt;br /&gt;
* &amp;lt;code&amp;gt;delegate&amp;lt;/code&amp;gt; (mandatory) — Name of the delegate to register the handler in.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (mandatory) — The method to call to retrieve the main page content.&lt;br /&gt;
* &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; (optional) — A method to call to retrieve the initialisation JS and the restrictions to apply to the whole handler. It can also return templates that can be used from JavaScript. You can learn more about this in the [[#Initialisation|Initialisation]] section.&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttocurrentuser&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForUser&amp;lt;/code&amp;gt; function. If true, the handler will only be shown for the current user. For more info about displaying the plugin only for certain users, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;restricttoenrolledcourses&amp;lt;/code&amp;gt; (optional) — Only used if the delegate has a &amp;lt;code&amp;gt;isEnabledForCourse&amp;lt;/code&amp;gt; function. If true or not defined, the handler will only be shown for courses the user is enrolled in. For more info about displaying the plugin only for certain courses, please see [[#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &amp;lt;code&amp;gt;styles&amp;lt;/code&amp;gt; (optional) — An array with two properties: &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;version&amp;lt;/code&amp;gt;. The URL should point to a CSS file, either using an absolute URL or a relative URL. This file will be downloaded and applied by the app. It&#039;s recommended to include styles that will only affect your plugin templates. The version number is used to determine if the file needs to be downloaded again, you should change the version number everytime you change the CSS file.&lt;br /&gt;
* &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; (optional) — If your plugin supports a component in the app different than the one defined by your plugin, you can use this property to specify it. For example, you can create a local plugin to support a certain course format, activity, etc. The component of your plugin in Moodle would be &amp;lt;code&amp;gt;local_whatever&amp;lt;/code&amp;gt;, but in &amp;lt;code&amp;gt;moodlecomponent&amp;lt;/code&amp;gt; you can specify that this handler will implement &amp;lt;code&amp;gt;format_whatever&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;mod_whatever&amp;lt;/code&amp;gt;. This property was introduced in the version 3.6.1 of the app.&lt;br /&gt;
===Options only for CoreMainMenuDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. Main Menu plugins are always displayed in the More tab, they cannot be displayed as tabs in the bottom bar.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreMainMenuHomeDelegate ===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseOptionsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
* &amp;lt;code&amp;gt;ismenuhandler&amp;lt;/code&amp;gt; (optional) — Supported from the 3.7.1 version of the app. Set it to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; if you want your plugin to be displayed in the contextual menu of the course instead of in the top tabs. The contextual menu is displayed when you click in the 3-dots button at the top right of the course.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseModuleDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (optional) — The function to call to retrieve the main page content. In this delegate the method is optional. If the method is not set, the module won&#039;t be clickable.&lt;br /&gt;
* &amp;lt;code&amp;gt;offlinefunctions&amp;lt;/code&amp;gt; (optional) — List of functions to call when prefetching the module. It can be a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; method or a WS. You can filter the params received by the WS. By default, WS will receive these params: &amp;lt;code&amp;gt;courseid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt;. Other valid values that will be added if they are present in the list of params: &amp;lt;code&amp;gt;courseids&amp;lt;/code&amp;gt; (it will receive a list with the courses the user is enrolled in), &amp;lt;code&amp;gt;{component}id&amp;lt;/code&amp;gt; (For example, &amp;lt;code&amp;gt;certificateid&amp;lt;/code&amp;gt;).&lt;br /&gt;
* &amp;lt;code&amp;gt;downloadbutton&amp;lt;/code&amp;gt; (optional) — Whether to display download button in the module. If not defined, the button will be shown if there is any offlinefunction.&lt;br /&gt;
* &amp;lt;code&amp;gt;isresource&amp;lt;/code&amp;gt; (optional) — Whether the module is a resource or an activity. Only used if there is any offline function. If your module relies on the &amp;lt;code&amp;gt;contents&amp;lt;/code&amp;gt; field, then it should be &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;updatesnames&amp;lt;/code&amp;gt; (optional) — Only used if there is any offline function. A regular expression to check if there&#039;s any update in the module. It will be compared to the result of &amp;lt;code&amp;gt;core_course_check_updates&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayopeninbrowser&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Open in browser&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayOpenInBrowser = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydescription&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Description&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayDescription = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayrefresh&amp;lt;/code&amp;gt; (optional) — Whether the module should display the &amp;quot;Refresh&amp;quot; option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayRefresh = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayprefetch&amp;lt;/code&amp;gt; (optional) — Whether the module should display the download option in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displayPrefetch = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysize&amp;lt;/code&amp;gt; (optional) — Whether the module should display the downloaded size in the top-right menu. This can be done in JavaScript too: &amp;lt;code&amp;gt;this.displaySize = false;&amp;lt;/code&amp;gt;. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; (optional) — It can be used to specify the supported features of the plugin. Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. It should be an array with features as keys (For example, &amp;lt;code&amp;gt;[FEATURE_NO_VIEW_LINK =&amp;gt; true&amp;lt;/code&amp;gt;). If you need to calculate this dynamically please see [[#Module_plugins:_dynamically_determine_if_a_feature_is_supported|Module plugins: dynamically determine if a feature is supported]]. Supported from the 3.6 version of the app.&lt;br /&gt;
* &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; (optional) — If set, this method will be called when the course is rendered and the HTML returned will be displayed in the course page for the module. Please notice the HTML returned should not contain directives or components, only default HTML. Supported from the 3.8 version of the app.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreCourseFormatDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;canviewallsections&amp;lt;/code&amp;gt; (optional) — Whether the course format allows seeing all sections in a single page. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;displayenabledownload&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, it&#039;s no longer used.&lt;br /&gt;
* &amp;lt;code&amp;gt;displaysectionselector&amp;lt;/code&amp;gt; (optional) — Deprecated in the 4.0 app, use &#039;&#039;displaycourseindex&#039;&#039; instead.&lt;br /&gt;
*&amp;lt;code&amp;gt;displaycourseindex&amp;lt;/code&amp;gt; (optional) — Whether the default course index should be displayed. Defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
===Options only for CoreUserDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — The type of the addon. The values accepted are &amp;lt;code&amp;gt;&#039;newpage&#039;&amp;lt;/code&amp;gt; (default) and &amp;lt;code&amp;gt;&#039;communication&#039;&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreSettingsDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for AddonMessageOutputDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (mandatory):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section.&lt;br /&gt;
** &amp;lt;code&amp;gt;icon&amp;lt;/code&amp;gt; — The name of an ionic icon. Valid strings can be found here: https://ionic.io/ionicons.&lt;br /&gt;
* &amp;lt;code&amp;gt;priority&amp;lt;/code&amp;gt; (optional) — Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
*&amp;lt;code&amp;gt;ptrenabled&amp;lt;/code&amp;gt; (optional) — Whether to enable pull-to-refresh gesture to refresh page content.&lt;br /&gt;
===Options only for CoreBlockDelegate===&lt;br /&gt;
* &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt; (optional):&lt;br /&gt;
** &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; — A language string identifier that was included in the &amp;lt;code&amp;gt;lang&amp;lt;/code&amp;gt; section. If this is not supplied, it will default to &amp;lt;code&amp;gt;&#039;plugins.block_{block-name}.pluginname&#039;&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;class&amp;lt;/code&amp;gt; — A CSS class. If this is not supplied, it will default to &amp;lt;code&amp;gt;block_{block-name}&amp;lt;/code&amp;gt;, where &amp;lt;code&amp;gt;{block-name}&amp;lt;/code&amp;gt; is the name of the block.&lt;br /&gt;
** &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; — Possible values are:&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;title&amp;quot;&amp;lt;/code&amp;gt; — Your block will only display the block title, and when it&#039;s clicked it will open a new page to display the block contents (the template returned by the block&#039;s method).&lt;br /&gt;
*** &amp;lt;code&amp;gt;&amp;quot;prerendered&amp;quot;&amp;lt;/code&amp;gt; — Your block will display the content and footer returned by the WebService to get the blocks (for example, &amp;lt;code&amp;gt;core_block_get_course_blocks&amp;lt;/code&amp;gt;), so your block&#039;s method will never be called.&lt;br /&gt;
*** Any other value — Your block will immediately call the method specified in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; and it will use the template to render the block.&lt;br /&gt;
* &amp;lt;code&amp;gt;fallback&amp;lt;/code&amp;gt; (optional) — This option allows you to specify a block to use in the app instead of your block. For example, you can make the app display the &amp;quot;My overview&amp;quot; block instead of your block in the app by setting &amp;lt;code&amp;gt;&#039;fallback&#039; =&amp;gt; &#039;myoverview&#039;&amp;lt;/code&amp;gt;. The fallback will only be used if you don&#039;t specify a &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; and the &amp;lt;code&amp;gt;type&amp;lt;/code&amp;gt; is different to &amp;lt;code&amp;gt;&#039;title&#039;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&#039;prerendered&#039;&amp;lt;/code&amp;gt;. Supported from the 3.9.0 version of the app.&lt;br /&gt;
==Delegates==&lt;br /&gt;
Delegates can be classified by type of plugin. For more info about type of plugins, please see the [[#Types_of_plugins|Types of plugins]] section.&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new items to the main menu (currently displayed at the bottom of the app). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreMainMenuHomeDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new tabs in the home page (by default the app is displaying the &amp;quot;Dashboard&amp;quot; and &amp;quot;Site home&amp;quot; tabs). &lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add new options in a course (Participants or Grades are examples of this type of delegate).&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting activity modules or resources.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate when you want to add additional options in the user profile page in the app.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting course formats. When you open a course from the course list in the mobile app, it will check if there is a &amp;lt;code&amp;gt;CoreCourseFormatDelegate&amp;lt;/code&amp;gt; handler for the format that site uses. If so, it will display the course using that handler. Otherwise, it will use the default app course format.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile course formats]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreSettingsDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to add a new option in the settings page.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonMessageOutputDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a message output plugin.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreBlockDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a block. For example, blocks can be displayed in Site Home, Dashboard and the Course page.&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question types.&lt;br /&gt;
&lt;br /&gt;
You can learn more about this at the [[Creating mobile question types]] page.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreQuestionBehaviourDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting question behaviours.&lt;br /&gt;
====&amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate for supporting user profile fields.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModQuizAccessRuleDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a quiz access rule.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonModAssignSubmissionDelegate&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;AddonModAssignFeedbackDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use these delegates to support assign submission or feedback plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;AddonWorkshopAssessmentStrategyDelegate&amp;lt;/code&amp;gt;====&lt;br /&gt;
You must use this delegate to support a workshop assessment strategy plugin.&lt;br /&gt;
===Pure JavaScript plugins===&lt;br /&gt;
These delegates require JavaScript to be supported. See [[#Initialisation|Initialisation]] for more information.&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreContentLinksDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreCourseModulePrefetchDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFileUploaderDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CorePluginFileDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;CoreFilterDelegate&amp;lt;/code&amp;gt;&lt;br /&gt;
==Available components and directives==&lt;br /&gt;
===Difference between components and directives===&lt;br /&gt;
A directive is usually represented as an HTML attribute, allows you to extend a piece of HTML with additional information or functionality. Example of directives are: &amp;lt;code&amp;gt;core-auto-focus&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*ngIf&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;ng-repeat&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components are also directives, but they are usually represented as an HTML tag and they are used to add custom elements to the app. Example of components are &amp;lt;code&amp;gt;ion-list&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-item&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;core-search-box&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Components and directives are Angular concepts; you can learn more about them and the components come out of the box with Ionic in the following links:&lt;br /&gt;
* [https://angular.io/guide/built-in-directives Angular directives documentation]&lt;br /&gt;
* [https://ionicframework.com/docs/components Ionic components]&lt;br /&gt;
===Custom core components and directives===&lt;br /&gt;
These are some useful custom components and directives that are only available in the Moodle App. Please note that this isn’t the full list of custom components and directives, it’s just an extract of the most common ones.&lt;br /&gt;
&lt;br /&gt;
You can find a full list of components and directives in the source code of the app, within [https://github.com/moodlehq/moodleapp/tree/master/src/core/components &amp;lt;code&amp;gt;src/core/components&amp;lt;/code&amp;gt;] and [https://github.com/moodlehq/moodleapp/tree/master/src/core/directives &amp;lt;code&amp;gt;src/core/directives&amp;lt;/code&amp;gt;].&lt;br /&gt;
====&amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;====&lt;br /&gt;
This directive formats the text and adds some directives needed for the app to work as it should. For example, it treats all links and all the embedded media so they work fine in the app. If some content in your template includes links or embedded media, please use this directive.&lt;br /&gt;
&lt;br /&gt;
This directive automatically applies &amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt; to all the links and embedded media.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;text&amp;lt;/code&amp;gt; (string) — The text to format.&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;adaptImg&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to adapt images to screen width. &lt;br /&gt;
* &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether all HTML tags should be removed.&lt;br /&gt;
* &amp;lt;code&amp;gt;singleLine&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether new lines should be removed to display all the text in single line. Only if &amp;lt;code&amp;gt;clean&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; (number) — Optional. Max height in pixels to render the content box. The minimum accepted value is 50. Using this parameter will force &amp;lt;code&amp;gt;display: block&amp;lt;/code&amp;gt; to calculate the height better. If you want to avoid this, use &amp;lt;code&amp;gt;class=&amp;quot;inline&amp;quot;&amp;lt;/code&amp;gt; at the same time to use &amp;lt;code&amp;gt;display: inline-block&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;fullOnClick&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether it should open a new page with the full contents on click. Only if &amp;lt;code&amp;gt;maxHeight&amp;lt;/code&amp;gt; is set and the content has been collapsed. &lt;br /&gt;
* &amp;lt;code&amp;gt;fullTitle&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;Description&amp;quot;&amp;lt;/code&amp;gt;. Title to use in full view.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-format-text text=&amp;quot;&amp;lt;% cm.description %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle a link. It performs several checks, like checking if the link needs to be opened in the app, and opens the link as it should (without overriding the app).&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;capture&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether the link needs to be captured by the app (check if the link can be handled by the app instead of opening it in a browser).&lt;br /&gt;
* &amp;lt;code&amp;gt;inApp&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to open in an embedded browser within the app or in the system browser.&lt;br /&gt;
* &amp;lt;code&amp;gt;autoLogin&amp;lt;/code&amp;gt; (string) — Optional, defaults to &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt;. If the link should be open with auto-login. Accepts the following values:&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;yes&amp;quot;&amp;lt;/code&amp;gt; — Always auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;no&amp;quot;&amp;lt;/code&amp;gt; — Never auto-login.&lt;br /&gt;
** &amp;lt;code&amp;gt;&amp;quot;check&amp;quot;&amp;lt;/code&amp;gt; — Auto-login only if it points to the current site.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a href=&amp;quot;&amp;lt;% cm.url %&amp;gt;&amp;quot; core-link&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-external-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to handle links to files and embedded files. This directive should be used in any link to a file or any embedded file that you want to have available when the app is offline. &lt;br /&gt;
&lt;br /&gt;
If a file is downloaded, its URL will be replaced by the local file URL.&lt;br /&gt;
&lt;br /&gt;
This directive is automatically applied to all the links and media inside &amp;lt;code&amp;gt;core-format-text&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;siteId&amp;lt;/code&amp;gt; (string) — Optional. Site ID to use. If not defined, it will use the id of the current site.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;img src=&amp;quot;&amp;lt;% event.iconurl %&amp;gt;&amp;quot; core-external-content component=&amp;quot;mod_certificate&amp;quot; componentId=&amp;quot;&amp;lt;% event.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-user-link&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to go to user profile on click. When the user clicks the element where this directive is attached, the right user profile will be opened.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;userId&amp;lt;/code&amp;gt; (number) — User id to open the profile.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — Optional. Course id to show the user info related to that course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;a ion-item core-user-link userId=&amp;quot;&amp;lt;% userid %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to handle a remote file. It shows the file name, icon (depending on mime type) and a button to download or refresh it. The user can identify if the file is downloaded or not based on the button.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;file&amp;lt;/code&amp;gt; (object) — The file. Must have a &amp;lt;code&amp;gt;filename&amp;lt;/code&amp;gt; property and either &amp;lt;code&amp;gt;fileurl&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;url&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component the file belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDelete&amp;lt;/code&amp;gt; (boolean) — Optional. Whether the file can be deleted.&lt;br /&gt;
* &amp;lt;code&amp;gt;alwaysDownload&amp;lt;/code&amp;gt; (boolean) — Optional. Whether it should always display the refresh button when the file is downloaded. Use it for files that you cannot determine if they&#039;re outdated or not.&lt;br /&gt;
* &amp;lt;code&amp;gt;canDownload&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether file can be downloaded. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-file&lt;br /&gt;
        [file]=&amp;quot;{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.url %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.name %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            filesize: &#039;&amp;lt;% issue.size %&amp;gt;&#039;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/core-file&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening a file. When the item with this directive is clicked, the file will be downloaded (if needed) and opened.&lt;br /&gt;
&lt;br /&gt;
It is usually recommended to use the &amp;lt;code&amp;gt;core-file&amp;lt;/code&amp;gt; component since it also displays the state of the file.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;core-download-file&amp;lt;/code&amp;gt; (object) — The file to download.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional. Component ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage (a button to download a file):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button&lt;br /&gt;
        [core-download-file]=&amp;quot;{&lt;br /&gt;
            fileurl: &amp;lt;% issue.url %&amp;gt;,&lt;br /&gt;
            timemodified: &amp;lt;% issue.timemodified %&amp;gt;,&lt;br /&gt;
            filesize: &amp;lt;% issue.size %&amp;gt;&lt;br /&gt;
        }&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        componentId=&amp;quot;&amp;lt;% cm.id %&amp;gt;&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.download | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-course-download-module-main-file&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to allow downloading and opening the main file of a module.&lt;br /&gt;
&lt;br /&gt;
When the item with this directive is clicked, the whole module will be downloaded (if needed) and its main file opened. This is meant for modules like &amp;lt;code&amp;gt;mod_resource&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This directive must receive either a &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; or a &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. If no files are provided, it will use &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt; (object) — Optional, required if module is not supplied. The module object.&lt;br /&gt;
* &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt; (number) — Optional, required if module is not supplied. The module ID.&lt;br /&gt;
* &amp;lt;code&amp;gt;courseId&amp;lt;/code&amp;gt; (number) — The course ID the module belongs to.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — Optional. Component to link the file to.&lt;br /&gt;
* &amp;lt;code&amp;gt;componentId&amp;lt;/code&amp;gt; (string|number) — Optional, defaults to the same value as &amp;lt;code&amp;gt;moduleId&amp;lt;/code&amp;gt;. Component ID to use in conjunction with the component.&lt;br /&gt;
* &amp;lt;code&amp;gt;files&amp;lt;/code&amp;gt; (object[]) — Optional. List of files of the module. If not provided, uses &amp;lt;code&amp;gt;module.contents&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot; &lt;br /&gt;
        courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        [files]=&amp;quot;[{&lt;br /&gt;
            fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;,&lt;br /&gt;
            filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;,&lt;br /&gt;
            timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;,&lt;br /&gt;
            mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;,&lt;br /&gt;
        }]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-navbar-buttons&amp;lt;/code&amp;gt;====&lt;br /&gt;
Component to add buttons to the app&#039;s header without having to place them inside the header itself. Using this component in a site plugin will allow adding buttons to the header of the current page.&lt;br /&gt;
&lt;br /&gt;
If this component indicates a position (start/end), the buttons will only be added if the header has some buttons in that position. If no start/end is specified, then the buttons will be added to the first &amp;lt;code&amp;gt;&amp;lt;ion-buttons&amp;gt;&amp;lt;/code&amp;gt; found in the header.&lt;br /&gt;
&lt;br /&gt;
You can use the &amp;lt;code&amp;gt;[hidden]&amp;lt;/code&amp;gt; input to hide all the inner buttons if a certain condition is met.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons end&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button (click)=&amp;quot;action()&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-icon slot=&amp;quot;icon-only&amp;quot; name=&amp;quot;funnel&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can also use this to add options to the context menu, for example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons&amp;gt;&lt;br /&gt;
    &amp;lt;core-context-menu&amp;gt;&lt;br /&gt;
        &amp;lt;core-context-menu-item&lt;br /&gt;
                [priority]=&amp;quot;500&amp;quot; content=&amp;quot;Nice boat&amp;quot; (action)=&amp;quot;boatFunction()&amp;quot;&lt;br /&gt;
                iconAction=&amp;quot;boat&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;/core-context-menu-item&amp;gt;&lt;br /&gt;
    &amp;lt;/core-context-menu&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Using icons ====&lt;br /&gt;
&lt;br /&gt;
===Specific component and directives for plugins===&lt;br /&gt;
These are component and directives created specifically for supporting Moodle plugins.&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to display a new content when clicked. This new content can be displayed in a new page or in the current page (only if the current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The params to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in v3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in same page or open a new one. &lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that doing &amp;lt;code&amp;gt;[useOtherData]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to go to a new content page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content &lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; &lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to load new content in current page using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-new-content&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot; samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.viewissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked. The action to do when the WS call is successful depends on the provided data: display a message, go back or refresh current view.&lt;br /&gt;
&lt;br /&gt;
If you want to load a new content when the WS call is done, please see [[#core-site-plugins-call-ws-new-content|&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The params for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;successMessage&amp;lt;/code&amp;gt; (string) — Message to show on success. If not supplied, no message. If supplied but empty, defaults to &amp;quot;Success&amp;quot;.&lt;br /&gt;
* &amp;lt;code&amp;gt;goBackOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to go back if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshOnSuccess&amp;lt;/code&amp;gt; (boolean) — Whether to refresh the current view if the WS call is successful.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to send some data to the server without using cache, displaying default messages and refreshing on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage successMessage&lt;br /&gt;
        refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to send some data to the server using cache without confirming, going back on success and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        goBackOnSuccess=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Same as the previous example, but implementing custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;certificateViewed($event)&amp;quot;&amp;gt;&lt;br /&gt;
     {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.certificateViewed = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-new-content&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS when the element is clicked and load a new content passing the WS result as arguments. This new content can be displayed in a new page or in the same page (only if current page is already displaying a site plugin content).&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t need to load some new content when done, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;confirmMessage&amp;lt;/code&amp;gt; (string) — Message to confirm the action when theuser clicks the element. If not supplied, no confirmation will be requested. If supplied but empty, &amp;quot;Are you sure?&amp;quot; will be used.&lt;br /&gt;
* &amp;lt;code&amp;gt;showError&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;. Whether to show an error message if the WS call fails. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; (string) — The component of the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; (string) — The method to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; (object) — The parameters to get the new content.&lt;br /&gt;
* &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; (string) — The title to display with the new content. Only if &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; is &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;samePage&amp;lt;/code&amp;gt; (boolean) — Optional, defaults to &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. Whether to display the content in the same page or open a new one.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherData&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the arguments for the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call. The format is the same as in &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &amp;lt;code&amp;gt;jsData&amp;lt;/code&amp;gt; (any) — JS variables to pass to the new page so they can be used in the template or JS. If &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; is supplied instead of an object, all initial variables from current page will be copied. This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;newContentPreSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call of the new content: whether to use cache or not, etc. This field was added in 3.6.0.&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Let&#039;s see some examples.&lt;br /&gt;
&lt;br /&gt;
A button to get some data from the server without using cache, showing default confirm and displaying a new page:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage&lt;br /&gt;
        title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot;&lt;br /&gt;
        method=&amp;quot;mobile_issues_view&amp;quot; [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A button to get some data from the server using cache, without confirm, displaying new content in same page and using &amp;lt;code&amp;gt;userid&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Same as the previous example, but implementing a custom JS code to run on success:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button core-site-plugins-call-ws-new-content&lt;br /&gt;
        name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        component=&amp;quot;mod_certificate&amp;quot; method=&amp;quot;mobile_issues_view&amp;quot;&lt;br /&gt;
        [args]=&amp;quot;{cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;}&amp;quot;&lt;br /&gt;
        samePage=&amp;quot;true&amp;quot; [useOtherData]=&amp;quot;[&#039;userid&#039;]&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getissued&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====&amp;lt;code&amp;gt;core-site-plugins-call-ws-on-load&amp;lt;/code&amp;gt;====&lt;br /&gt;
Directive to call a WS as soon as the template is loaded. This directive is meant for actions to do in the background, like calling logging Web Services.&lt;br /&gt;
&lt;br /&gt;
If you want to call a WS when the user clicks on a certain element, please see [[#core-site-plugins-call-ws|&amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt;]].&lt;br /&gt;
* &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; (string) — The name of the WS to call.&lt;br /&gt;
* &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; (object) — The parameters for the WS call.&lt;br /&gt;
* &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; (object) — Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; (any) — Whether to include &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; (from the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; or an empty string) all the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that &amp;lt;code&amp;gt;[useOtherDataForWS]=&amp;quot;&amp;quot;&amp;lt;/code&amp;gt; is the same as not supplying it, so nothing will be copied. Also, objects or arrays in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; will be converted to a JSON encoded string.&lt;br /&gt;
* &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; (string) — ID or name to identify a form in the template. The form will be obtained from &amp;lt;code&amp;gt;document.forms&amp;lt;/code&amp;gt;. If supplied and a form is found, the form data will be retrieved and sent to the new &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call. If your form contains an &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt;, please see [[#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS]].&lt;br /&gt;
* &amp;lt;code&amp;gt;onSuccess&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onError&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in 3.5.2.&lt;br /&gt;
* &amp;lt;code&amp;gt;onDone&amp;lt;/code&amp;gt; (Function) — A function to call when the WS call finishes (either success or fail). This field was added in 3.5.2.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;span core-site-plugins-call-ws-on-load&lt;br /&gt;
        name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot;&lt;br /&gt;
        [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the JavaScript side, you would do:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.callDone = function(result) {&lt;br /&gt;
    // Code to run when the WS call is successful.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Advanced features==&lt;br /&gt;
===Display the plugin only if certain conditions are met===&lt;br /&gt;
You might want to display your plugin in the mobile app only if certain dynamic conditions are met, so the plugin would be displayed only for some users. This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section ahead).&lt;br /&gt;
&lt;br /&gt;
All initialisation methods are called as soon as your plugin is retrieved. If you don&#039;t want your plugin to be displayed for the current user, then you should return the following in the initialisation method (only for Moodle site 3.8 and onwards):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;disabled&#039; =&amp;gt; true];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If the Moodle site is older than 3.8, then the initialisation method should return this instead:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&#039;javascript&#039; =&amp;gt; &#039;this.HANDLER_DISABLED&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
On the other hand, you might want to display a plugin only for certain courses (&amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt;) or only if the user is viewing certain users&#039; profiles (&amp;lt;code&amp;gt;CoreUserDelegate&amp;lt;/code&amp;gt;). This can be achieved with the initialisation method too.&lt;br /&gt;
&lt;br /&gt;
In the initialisation method you can return a &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; property with two fields in it: &amp;lt;code&amp;gt;courses&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;users&amp;lt;/code&amp;gt;. If you return a list of courses IDs in this property, then your plugin will only be displayed when the user views any of those courses. In the same way, if you return a list of user IDs then your plugin will only be displayed when the user views any of those users&#039; profiles.&lt;br /&gt;
===Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;===&lt;br /&gt;
The values returned by the functions in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; are added to a variable so they can be used both in JavaScript and in templates. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call is added to a variable named &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt;, while the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; WS call is added to a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by an initialisation call will be passed to the JS and template of all the &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; calls in that handler. The &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by a &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call will only be passed to the JS and template returned by that &amp;lt;code&amp;gt;get_content&amp;lt;/code&amp;gt; call.&lt;br /&gt;
&lt;br /&gt;
This means that, in your JavaScript, you can access and use the data like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.CONTENT_OTHERDATA.myVar;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And in the template you could use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
{{ CONTENT_OTHERDATA.myVar }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; is the name we put to one of our variables, it can be any name that you want. In the example above, this is the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
[&#039;myVar&#039; =&amp;gt; &#039;Initial value&#039;]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Example====&lt;br /&gt;
In our plugin, we want to display an input text with a certain initial value. When the user clicks a button, we want the value in the input to be sent to a certain Web Service. This can be done using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We will return the initial value of the input in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; of our PHP method:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;myVar&#039; =&amp;gt; &#039;My initial value&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then in the template we will use it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item text-wrap&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label position=&amp;quot;stacked&amp;quot;&amp;gt;{{ &#039;plugin.mod_certificate.textlabel | translate }}&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;text&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.myVar&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws name=&amp;quot;mod_certificate_my_webservice&amp;quot; [useOtherDataForWS]=&amp;quot;[&#039;myVar&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
    &amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we are creating an input text and we use &amp;lt;code&amp;gt;[(ngModel)]&amp;lt;/code&amp;gt; to use the value in &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; as the initial value and to store the changes in the same &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This means that the initial value of the input will be &amp;quot;My initial value&amp;quot;, and if the user changes the value of the input these changes will be applied to the &amp;lt;code&amp;gt;myVar&amp;lt;/code&amp;gt; variable. This is called 2-way data binding in Angular.&lt;br /&gt;
&lt;br /&gt;
Then we add a button to send this data to a WS, and for that we use the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive. We use the &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt; attribute to specify which variable from &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; we want to send to our WebService. So if the user enters &amp;quot;A new value&amp;quot; in the input and then clicks the button, it will call the WebService &amp;lt;code&amp;gt;mod_certificate_my_webservice&amp;lt;/code&amp;gt; and will send as a parameter &amp;lt;code&amp;gt;[&#039;myVar&#039; =&amp;gt; &#039;A new value&#039;]&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
We can also achieve the same result using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; attribute of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive instead of using &amp;lt;code&amp;gt;useOtherDataForWS&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; color=&amp;quot;light&amp;quot; core-site-plugins-call-ws &lt;br /&gt;
        name=&amp;quot;mod_certificate_my_webservice&amp;quot; [params]=&amp;quot;{myVar: CONTENT_OTHERDATA.myVar}&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.send | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The Web Service call will be exactly the same with both versions.&lt;br /&gt;
&lt;br /&gt;
Notice that this example could be done without using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; too, using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; input of the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive.&lt;br /&gt;
===Running JS code after a content template has loaded===&lt;br /&gt;
When you return JavaScript code from a handler function using the &amp;lt;code&amp;gt;javascript&amp;lt;/code&amp;gt; array key, this code is executed immediately after the web service call returns, which may be before the returned template has been rendered into the DOM. &lt;br /&gt;
&lt;br /&gt;
If your code needs to run after the DOM has been updated, you can use &amp;lt;code&amp;gt;setTimeout&amp;lt;/code&amp;gt; to call it. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
return [&lt;br /&gt;
    &#039;template&#039; =&amp;gt; [&lt;br /&gt;
        // ...&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;javascript&#039; =&amp;gt; &#039;setTimeout(function() { console.log(&amp;quot;DOM is available now&amp;quot;); });&#039;,&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; &#039;&#039;,&lt;br /&gt;
    &#039;files&#039; =&amp;gt; [],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice that if you wanted to write a lot of code here, you might be better off putting it in a function defined in the response from an initialisation template, so that it does not get loaded again with each page of content.&lt;br /&gt;
===JS functions visible in the templates===&lt;br /&gt;
The app provides some JavaScript functions that can be used from the templates to update, refresh or view content. These are the functions:&lt;br /&gt;
* &amp;lt;code&amp;gt;openContent(title: string, args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Open a new page to display some new content. You need to specify the &amp;lt;code&amp;gt;title&amp;lt;/code&amp;gt; of the new page and the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
* &amp;lt;code&amp;gt;refreshContent(showSpinner = true)&amp;lt;/code&amp;gt; — Refresh the current content. By default, it will display a spinner while refreshing. If you don&#039;t want it to be displayed, you should pass &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; as a parameter.&lt;br /&gt;
* &amp;lt;code&amp;gt;updateContent(args: any, component?: string, method?: string)&amp;lt;/code&amp;gt; — Refresh the current content using different parameters. You need to specify the &amp;lt;code&amp;gt;args&amp;lt;/code&amp;gt; to send to the method. If &amp;lt;code&amp;gt;component&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Group selector=====&lt;br /&gt;
Imagine we have an activity that uses groups and we want to let the user select which group they want to see. A possible solution would be to return all the groups in the same template (hidden), and then show the group user selects. However, we can make it more dynamic and return only the group the user is requesting.&lt;br /&gt;
&lt;br /&gt;
To do so, we&#039;ll use a drop down to select the group. When the user selects a group using this drop down, we&#039;ll update the page content to display the new group.&lt;br /&gt;
&lt;br /&gt;
The main difficulty in this is to tell the view which group needs to be selected when the view is loaded. There are 2 ways to do it: using plain HTML or using Angular&#039;s &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;.&lt;br /&gt;
======Using plain HTML======&lt;br /&gt;
We need to add a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; attribute to the option that needs to be selected. To do so, we need to pre-caclulate the selected option in the PHP code:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// Detect which group is selected.&lt;br /&gt;
foreach ($groups as $gid=&amp;gt;$group) {&lt;br /&gt;
    $group-&amp;gt;selected = $gid === $groupid;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$data = [&lt;br /&gt;
    &#039;cmid&#039; =&amp;gt; $cm-&amp;gt;id,&lt;br /&gt;
    &#039;courseid&#039; =&amp;gt; $args-&amp;gt;courseid,&lt;br /&gt;
    &#039;groups&#039; =&amp;gt; $groups,&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the code above, we&#039;re retrieving the groups the user can see and then we&#039;re adding a &amp;lt;code&amp;gt;selected&amp;lt;/code&amp;gt; boolean to each one to determine which one needs to be selected in the drop down. Finally, we pass the list of groups to the template.&lt;br /&gt;
&lt;br /&gt;
In the template, we display the drop down like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: $event})&amp;quot; interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot; &amp;lt;%#selected%&amp;gt;selected&amp;lt;%/selected%&amp;gt; &amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The &amp;lt;code&amp;gt;ionChange&amp;lt;/code&amp;gt; function will be called every time the user selects a different group with the drop down. We&#039;re using the &amp;lt;code&amp;gt;updateContent&amp;lt;/code&amp;gt; function to update the current view using the new group. &amp;lt;code&amp;gt;$event&amp;lt;/code&amp;gt; is an Angular variable that will have the selected value (in our case, the group ID that was just selected). This is enough to make the group selector work.&lt;br /&gt;
======Using &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt;======&lt;br /&gt;
&amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; is an Angular directive that allows storing the value of a certain input or select in a JavaScript variable, and also the opposite way: tell the input or select which value to set. The main problem is that we cannot initialise a JavaScript variable from the template, so we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
In the PHP function we&#039;ll return the group that needs to be selected in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$groupid = empty($args-&amp;gt;group) ? 0 : $args-&amp;gt;group; // By default, group 0.&lt;br /&gt;
$groups = groups_get_activity_allowed_groups($cm, $user-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
return [&lt;br /&gt;
    &#039;templates&#039; =&amp;gt; [&lt;br /&gt;
        [&lt;br /&gt;
            &#039;id&#039; =&amp;gt; &#039;main&#039;,&lt;br /&gt;
            &#039;html&#039; =&amp;gt; $OUTPUT-&amp;gt;render_from_template(&#039;mod_certificate/mobile_view_page&#039;, $data),&lt;br /&gt;
        ],&lt;br /&gt;
    ],&lt;br /&gt;
    &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
        &#039;group&#039; =&amp;gt; $groupid,&lt;br /&gt;
    ],&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we don&#039;t need to iterate over the groups array like in the plain HTML example. However, now we&#039;re returning the group id in the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; array. As it&#039;s explained in the [[#Using_otherdata|Using &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;]] section, this &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; is visible in the templates inside a variable named &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. So in the template we&#039;ll use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-select [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.group&amp;quot;&lt;br /&gt;
        (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: CONTENT_OTHERDATA.group})&amp;quot;&lt;br /&gt;
        interface=&amp;quot;popover&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;%#groups%&amp;gt;&lt;br /&gt;
        &amp;lt;ion-option value=&amp;quot;&amp;lt;% id %&amp;gt;&amp;quot;&amp;gt;&amp;lt;% name %&amp;gt;&amp;lt;/ion-option&amp;gt;&lt;br /&gt;
    &amp;lt;%/groups%&amp;gt;&lt;br /&gt;
&amp;lt;/ion-select&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Use the rich text editor===&lt;br /&gt;
The rich text editor included in the app requires a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt; to work. You can use the &amp;lt;code&amp;gt;FormBuilder&amp;lt;/code&amp;gt; library to create this control (or to create a whole &amp;lt;cide&amp;gt;FormGroup if you prefer).&lt;br /&gt;
&lt;br /&gt;
With the following JavaScript you&#039;ll be able to create a &amp;lt;code&amp;gt;FormControl&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.control = this.FormBuilder.control(this.CONTENT_OTHERDATA.rte);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above we&#039;re using a value returned in &amp;lt;code&amp;gt;OTHERDATA&amp;lt;/code&amp;gt; as the initial value of the rich text editor, but you can use whatever you want.&lt;br /&gt;
&lt;br /&gt;
Then you need to pass this control to the rich text editor in your template:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-item&amp;gt;&lt;br /&gt;
    &amp;lt;core-rich-text-editor item-content [control]=&amp;quot;control&amp;quot; placeholder=&amp;quot;Enter your text here&amp;quot; name=&amp;quot;rte_answer&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;/core-rich-text-editor&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Finally, there are several ways to send the value in the rich text editor to a Web Service to save it. This is one of the simplest options:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_webservice&amp;quot; [params]=&amp;quot;{rte: control.value}&amp;quot; ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re passing the value of the rich text editor as a parameter to our Web Service.&lt;br /&gt;
===Initialisation===&lt;br /&gt;
All handlers can specify an &amp;lt;code&amp;gt;init&amp;lt;/code&amp;gt; method in the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; file. This method is meant to return some JavaScript code that needs to be executed as soon as the plugin is retrieved.&lt;br /&gt;
&lt;br /&gt;
When the app retrieves all the handlers, the first thing it will do is call the &amp;lt;code&amp;gt;tool_mobile_get_content&amp;lt;/code&amp;gt; Web Service with the initialisation method. This WS call will only receive the default arguments.&lt;br /&gt;
&lt;br /&gt;
The app will immediately execute the JavaScript code returned by this WS call. This JavaScript can be used to manually register your handlers in the delegates you want, without having to rely on the default handlers built based on the &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; data.&lt;br /&gt;
&lt;br /&gt;
The templates returned by this method will be added to a &amp;lt;code&amp;gt;INIT_TEMPLATES&amp;lt;/code&amp;gt; variable that will be passed to all the JavaScript code of that handler. This means that the JavaScript returned by the initialisation method or the main method can access any of the templates HTML like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.INIT_TEMPLATES[&#039;main&#039;];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In this case, &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is the ID of the template we want to use.&lt;br /&gt;
&lt;br /&gt;
The same happens with the &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; returned by the initialisation method, it is added to an &amp;lt;code&amp;gt;INIT_OTHERDATA&amp;lt;/code&amp;gt; variable.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;restrict&amp;lt;/code&amp;gt; field returned by this call will be used to determine if your handler is enabled or not. For example, if your handler is for the delegate &amp;lt;code&amp;gt;CoreCourseOptionsDelegate&amp;lt;/code&amp;gt; and you return a list of course ids in &amp;lt;code&amp;gt;restrict.courses&amp;lt;/code&amp;gt;, then your handler will only be enabled in the courses you returned. This only applies to the default handlers, if you register your own handler using the JavaScript code then you should check yourself if the handler is enabled.&lt;br /&gt;
&lt;br /&gt;
Finally, if you return an object in this initialisation JavaScript code, all the properties of that object will be passed to all the JavaScript code of that handler so you can use them when the code is run. For example, if your JavaScript code does something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    MyAddonClass: new MyAddonClass()&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then, for the rest of JavaScript code of your handler (for example, the main method) you can use this variable like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.MyAddonClass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Examples====&lt;br /&gt;
=====Link handlers=====&lt;br /&gt;
A link handler allows you to decide what to do when a link with a certain URL is clicked. This is useful, for example, to open your plugin page when a link to your plugin is clicked.&lt;br /&gt;
&lt;br /&gt;
After the 4.0 version, the Moodle app automatically creates two link handlers for module plugins, you don&#039;t need to create them in your plugin&#039;s Javascript code anymore:&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginanme/view.php?id=X&#039;&#039;. When this link is clicked, it will open your module in the app.&lt;br /&gt;
* A handler to treat links to &#039;&#039;mod/pluginname/index.php?id=X&#039;&#039;. When this link is clicked, it will open a page in the app listing all the modules of your type inside a certain course.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Link handlers have some advanced features that allow you to change how links behave under different conditions.&lt;br /&gt;
======Patterns======&lt;br /&gt;
You can define a Regular Expression pattern to match certain links. This will apply the handler only to links that match the pattern.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = RegExp(&#039;\/mod\/foo\/specialpage.php&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Priority======&lt;br /&gt;
Multiple link handlers may apply to a given link. You can define the order of precedence by setting the priority; the handler with the highest priority will be used.&lt;br /&gt;
&lt;br /&gt;
All default handlers have a priority of 0, so 1 or higher will override the default.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Multiple actions======&lt;br /&gt;
Once a link has been matched, the handler&#039;s &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method determines what the link should do. This method has access to the URL and its parameters.&lt;br /&gt;
&lt;br /&gt;
Different actions can be returned depending on different conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {&lt;br /&gt;
        return [&lt;br /&gt;
            {&lt;br /&gt;
                action: function(siteId, navCtrl) {&lt;br /&gt;
                    // The actual behaviour of the link goes here.&lt;br /&gt;
                },&lt;br /&gt;
                sites: [&lt;br /&gt;
                    // ...&lt;br /&gt;
                ],&lt;br /&gt;
            },&lt;br /&gt;
            {&lt;br /&gt;
                // ...&lt;br /&gt;
            },&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once handlers have been matched for a link, the actions will be fetched for all the matching handlers, in priorty order. The first valid action will be used to open the link.&lt;br /&gt;
&lt;br /&gt;
If your handler is matched with a link, but a condition assessed in the &amp;lt;code&amp;gt;getActions()&amp;lt;/code&amp;gt; method means you want to revert to the next highest priorty handler, you can invalidate your action by settings its sites propety to an empty array.&lt;br /&gt;
======Complex example======&lt;br /&gt;
This will match all URLs containing &amp;lt;code&amp;gt;/mod/foo/&amp;lt;/code&amp;gt;, and force those with an id parameter that&#039;s not in the &amp;lt;code&amp;gt;supportedModFoos&amp;lt;/code&amp;gt; array to open in the user&#039;s browser, rather than the app.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
const that = this;&lt;br /&gt;
const supportedModFoos = [...];&lt;br /&gt;
&lt;br /&gt;
class AddonModFooLinkHandler extends this.CoreContentLinksHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.pattern = new RegExp(&#039;\/mod\/foo\/&#039;);&lt;br /&gt;
        this.name = &#039;AddonModFooLinkHandler&#039;;&lt;br /&gt;
        this.priority = 1;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getActions(siteIds, url, params) {     &lt;br /&gt;
        const action = {&lt;br /&gt;
            action() {&lt;br /&gt;
                that.CoreUtilsProvider.openInBrowser(url);&lt;br /&gt;
            },&lt;br /&gt;
        };&lt;br /&gt;
&lt;br /&gt;
        if (supportedModFoos.indexOf(parseInt(params.id)) !== -1) {&lt;br /&gt;
            action.sites = [];&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return [action];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreContentLinksDelegate.registerHandler(new AddonModFooLinkHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Module prefetch handler=====&lt;br /&gt;
The &amp;lt;code&amp;gt;CoreCourseModuleDelegate&amp;lt;/code&amp;gt; handler allows you to define a list of offline functions to prefetch a module. However, you might want to create your own prefetch handler to determine what needs to be downloaded. For example, you might need to chain WS calls (pass the result of a WS call to the next one), and this cannot be done using offline functions.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to create a prefetch handler using the initialisation JS:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
// Create a class that extends from CoreCourseActivityPrefetchHandlerBase.&lt;br /&gt;
class AddonModCertificateModulePrefetchHandler extends CoreCourseActivityPrefetchHandlerBase {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        super();&lt;br /&gt;
&lt;br /&gt;
        this.name = &#039;AddonModCertificateModulePrefetchHandler&#039;;&lt;br /&gt;
        this.modName = &#039;certificate&#039;;&lt;br /&gt;
&lt;br /&gt;
        // This must match the plugin identifier from db/mobile.php,&lt;br /&gt;
        // otherwise the download link in the context menu will not update correctly.&lt;br /&gt;
        this.component = &#039;mod_certificate&#039;;&lt;br /&gt;
        this.updatesNames = /^configuration$|^.*files$/;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Override the prefetch call.&lt;br /&gt;
    prefetch(module, courseId, single, dirPath) {&lt;br /&gt;
        return this.prefetchPackage(module, courseId, single, prefetchCertificate);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function prefetchCertificate(module, courseId, single, siteId) {&lt;br /&gt;
    // Perform all the WS calls.&lt;br /&gt;
    // You can access most of the app providers using that.ClassName. E.g. that.CoreWSProvider.call().&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseModulePrefetchDelegate.registerHandler(new AddonModCertificateModulePrefetchHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
One relatively simple full example is where you have a function that needs to work offline, but it has an additional argument other than the standard ones. You can imagine for this an activity like the book module, where it has multiple pages for the same &amp;lt;code&amp;gt;cmid&amp;lt;/code&amp;gt;. The app will not automatically work with this situation — it will call the offline function with the standard arguments only — so you won&#039;t be able to prefetch all the possible parameters.&lt;br /&gt;
&lt;br /&gt;
To deal with this, you need to implement a web service in your Moodle component that returns the list of possible extra arguments, and then you can call this web service and loop around doing the same thing the app does when it prefetches the offline functions. Here is an example from a third-party module (showing only the actual prefetch function, the rest of the code is as above) where there are multiple values of a custom &amp;lt;code&amp;gt;section&amp;lt;/code&amp;gt; parameter for the mobile function &amp;lt;code&amp;gt;mobile_document_view&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
function prefetchOucontent(module, courseId, single, siteId) {&lt;br /&gt;
    var component = &#039;mod_oucontent&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Get the site, first.&lt;br /&gt;
    return that.CoreSitesProvider.getSite(siteId).then(function(site) {&lt;br /&gt;
        // Read the list of pages in this document using a web service.&lt;br /&gt;
        return site.read(&#039;mod_oucontent_get_page_list&#039;, {&#039;cmid&#039;: module.id}).then(function(response) {&lt;br /&gt;
            var promises = [];&lt;br /&gt;
&lt;br /&gt;
            // For each page, read and process the page - this is a copy of logic in the app at&lt;br /&gt;
            // siteplugins.ts (prefetchFunctions), but modified to add the custom argument.&lt;br /&gt;
            for(var i = 0; i &amp;lt; response.length; i++) {&lt;br /&gt;
                var args = {&lt;br /&gt;
                    courseid: courseId,&lt;br /&gt;
                    cmid: module.id,&lt;br /&gt;
                    userid: site.getUserId()&lt;br /&gt;
                };&lt;br /&gt;
                if (response[i] !== &#039;&#039;) {&lt;br /&gt;
                    args.section = response[i];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                promises.push(that.CoreSitePluginsProvider.getContent(&lt;br /&gt;
                        component, &#039;mobile_document_view&#039;, args).then(&lt;br /&gt;
                        function(result) {&lt;br /&gt;
                            var subPromises = [];&lt;br /&gt;
                            if (result.files &amp;amp;&amp;amp; result.files.length) {&lt;br /&gt;
                                subPromises.push(that.CoreFilepoolProvider.downloadOrPrefetchFiles(&lt;br /&gt;
                                        site.id, result.files, true, false, component, module.id));&lt;br /&gt;
                            }&lt;br /&gt;
                            return Promise.all(subPromises);&lt;br /&gt;
                        }));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            return Promise.all(promises);&lt;br /&gt;
        });&lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=====Single activity course format=====&lt;br /&gt;
In the following example, the value of &amp;lt;code&amp;gt;INIT_TEMPLATES[&#039;main&#039;]&amp;lt;/code&amp;gt; is:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;componentClass&amp;quot; [data]=&amp;quot;data&amp;quot;&amp;gt;&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This template is returned by the initialisation method. And this is the JavaScript code returned:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatComponent {&lt;br /&gt;
&lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.data = {};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    ngOnChanges(changes) {&lt;br /&gt;
        var self = this;&lt;br /&gt;
&lt;br /&gt;
        if (this.course &amp;amp;&amp;amp; this.sections &amp;amp;&amp;amp; this.sections.length) {&lt;br /&gt;
            var module = this.sections[0] &amp;amp;&amp;amp; this.sections[0].modules &amp;amp;&amp;amp; this.sections[0].modules[0];&lt;br /&gt;
            if (module &amp;amp;&amp;amp; !this.componentClass) {&lt;br /&gt;
                that.CoreCourseModuleDelegate.getMainComponent(that.Injector, this.course, module).then((component) =&amp;gt; {&lt;br /&gt;
                    self.componentClass = component || that.CoreCourseUnsupportedModuleComponent;&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.data.courseId = this.course.id;&lt;br /&gt;
            this.data.module = module;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    doRefresh(refresher, done) {&lt;br /&gt;
        return Promise.resolve(this.dynamicComponent.callComponentFunction(&amp;quot;doRefresh&amp;quot;, [refresher, done]));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class AddonSingleActivityFormatHandler {&lt;br /&gt;
    &lt;br /&gt;
    constructor() {&lt;br /&gt;
        this.name = &#039;singleactivity&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    isEnabled() {&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    canViewAllSections() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseTitle(course, sections) {&lt;br /&gt;
        if (sections &amp;amp;&amp;amp; sections[0] &amp;amp;&amp;amp; sections[0].modules &amp;amp;&amp;amp; sections[0].modules[0]) {&lt;br /&gt;
            return sections[0].modules[0].name;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return course.fullname || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displayEnableDownload() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    displaySectionSelector() {&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    getCourseFormatComponent() {&lt;br /&gt;
        return that.CoreCompileProvider.instantiateDynamicComponent(that.INIT_TEMPLATES[&#039;main&#039;], AddonSingleActivityFormatComponent);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseFormatDelegate.registerHandler(new AddonSingleActivityFormatHandler());&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using the JavaScript API===&lt;br /&gt;
The JavaScript API is only supported by the delegates specified in the [[#Templates_downloaded_on_login_and_rendered_using_JS_data_2|Templates downloaded on login and rendered using JS data]] section. This API allows you to override any of the functions of the default handler.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; specified in a handler registered in the &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; will be called immediately after the initialisation method, and the JavaScript returned by this method will be run. If this JavaScript code returns an object with certain functions, these functions will override the ones in the default handler.&lt;br /&gt;
&lt;br /&gt;
For example, if the JavaScript returned by the method returns something like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The the &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function of the default handler will be overridden by the returned &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
The default handler for &amp;lt;code&amp;gt;CoreUserProfileFieldDelegate&amp;lt;/code&amp;gt; only has 2 functions: &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;getData&amp;lt;/code&amp;gt;. In addition, the JavaScript code can return an extra function named &amp;lt;code&amp;gt;componentInit&amp;lt;/code&amp;gt; that will be executed when the component returned by &amp;lt;code&amp;gt;getComponent&amp;lt;/code&amp;gt; is initialised.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to support the text user profile field using this API:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
var result = {&lt;br /&gt;
    componentInit: function() {&lt;br /&gt;
        if (this.field &amp;amp;&amp;amp; this.edit &amp;amp;&amp;amp; this.form) {&lt;br /&gt;
            this.field.modelName = &#039;profile_field_&#039; + this.field.shortname;&lt;br /&gt;
&lt;br /&gt;
            if (this.field.param2) {&lt;br /&gt;
                this.field.maxlength = parseInt(this.field.param2, 10) || &#039;&#039;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.field.inputType = that.CoreUtilsProvider.isTrueOrOne(this.field.param3) ? &#039;password&#039; : &#039;text&#039;;&lt;br /&gt;
&lt;br /&gt;
            var formData = {&lt;br /&gt;
                value: this.field.defaultdata,&lt;br /&gt;
                disabled: this.disabled,&lt;br /&gt;
            };&lt;br /&gt;
&lt;br /&gt;
            this.form.addControl(this.field.modelName,&lt;br /&gt;
                that.FormBuilder.control(formData, this.field.required &amp;amp;&amp;amp; !this.field.locked ? that.Validators.required : null));&lt;br /&gt;
        }&lt;br /&gt;
    },&lt;br /&gt;
    getData: function(field, signup, registerAuth, formValues) {&lt;br /&gt;
        var name = &#039;profile_field_&#039; + field.shortname;&lt;br /&gt;
&lt;br /&gt;
        return {&lt;br /&gt;
            type: &amp;quot;text&amp;quot;,&lt;br /&gt;
            name: name,&lt;br /&gt;
            value: that.CoreTextUtilsProvider.cleanTags(formValues[name]),&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Translate dynamic strings===&lt;br /&gt;
If you wish to have an element that displays a localised string based on value from your template you can doing something like:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        {{ &#039;plugin.mod_myactivity.&amp;lt;% status %&amp;gt;&#039; | translate }}&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This could save you from having to write something like when only one value should be displayed:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content&amp;gt;&lt;br /&gt;
        &amp;lt;%#isedting%&amp;gt;{{ &#039;plugin.mod_myactivity.editing&#039; | translate }}&amp;lt;%/isediting%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isopen%&amp;gt;{{ &#039;plugin.mod_myactivity.open&#039; | translate }}&amp;lt;%/isopen%&amp;gt;&lt;br /&gt;
        &amp;lt;%#isclosed%&amp;gt;{{ &#039;plugin.mod_myactivity.closed&#039; | translate }}&amp;lt;%/isclosed%&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-card-content&amp;gt;&lt;br /&gt;
&amp;lt;/ion-card&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Using strings with dates===&lt;br /&gt;
If you have a string that you wish to pass a formatted date, for example in the Moodle language file you have:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$string[&#039;strwithdate&#039;] = &#039;This string includes a date of {$a-&amp;gt;date} in the middle of it.&#039;;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can localise the string correctly in your template using something like the following:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+handlebars&amp;quot;&amp;gt;&lt;br /&gt;
{{ &#039;plugin.mod_myactivity.strwithdate&#039; | translate: {$a: { date: &amp;lt;% timestamp %&amp;gt; * 1000 | coreFormatDate: &amp;quot;dffulldate&amp;quot; } } }}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
A Unix timestamp must be multiplied by 1000 as the Mobile App expects millisecond timestamps, whereas Unix timestamps are in seconds.&lt;br /&gt;
===Support push notification clicks===&lt;br /&gt;
If your plugin sends push notifications to the app, you might want to open a certain page in the app when the notification is clicked. There are several ways to achieve this.&lt;br /&gt;
&lt;br /&gt;
The easiest way is to include a &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; in your notification. When the notification is clicked, the app will try to open the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;contexturl&amp;lt;/code&amp;gt; will also be displayed in web. If you want to use a specific URL for the app, different than the one displayed in web, you can do so by returning a &amp;lt;code&amp;gt;customdata&amp;lt;/code&amp;gt; array that contains an &amp;lt;code&amp;gt;appurl&amp;lt;/code&amp;gt; property:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$notification-&amp;gt;customdata = [&lt;br /&gt;
    &#039;appurl&#039; =&amp;gt; $myurl-&amp;gt;out(),&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In both cases you will have to create a link handler to treat the URL. For more info on how to create the link handler, please see [[#Advanced_link_handler|how to create an advanced link handler]].&lt;br /&gt;
&lt;br /&gt;
If you want to do something that only happens when the notification is clicked, not when the link is clicked, you&#039;ll have to implement a push click handler yourself. The way to create it is similar to [[#Advanced_link_handler|creating an advanced link handler]], but you&#039;ll have to use &amp;lt;code&amp;gt;CorePushNotificationsDelegate&amp;lt;/code&amp;gt; and your handler will have to implement the properties and functions defined in the [https://github.com/moodlehq/moodleapp/blob/master/src/core/features/pushnotifications/services/push-delegate.ts#L27 CorePushNotificationsClickHandler] interface.&lt;br /&gt;
===Implement a module similar to mod_label===&lt;br /&gt;
In Moodle 3.8 or higher, if your plugin doesn&#039;t support &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt; and you don&#039;t specify a &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; then the module will only display the module description in the course page and it won&#039;t be clickable in the app, just like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt;. You can decide if you want the module icon to be displayed or not (if you don&#039;t want it to be displayed, then don&#039;t define it in &amp;lt;code&amp;gt;displaydata&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
However, if your plugin needs to work in previous versions of Moodle or you want to display something different than the description then you need a different approach.&lt;br /&gt;
&lt;br /&gt;
If your plugin wants to render something in the course page instead of just the module name and description you should specify the &amp;lt;code&amp;gt;coursepagemethod&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. The template returned by this method will be rendered in the course page. Please notice the HTML returned should not contain directives or components, only plain HTML.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want your module to be clickable then you just need to remove &amp;lt;code&amp;gt;method&amp;lt;/code&amp;gt; from &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt;. With these 2 changes you can have a module that behaves like &amp;lt;code&amp;gt;mod_label&amp;lt;/code&amp;gt; in the app.&lt;br /&gt;
===Use Ionic navigation lifecycle functions===&lt;br /&gt;
Ionic let pages define some functions that will be called when certain navigation lifecycle events happen. For more info about these functions, see [https://ionicframework.com/docs/api/router-outlet Ionic&#039;s documentation].&lt;br /&gt;
&lt;br /&gt;
You can define these functions in your plugin javascript:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.ionViewWillLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In addition to that, you can also implement &amp;lt;code&amp;gt;canLeave&amp;lt;/code&amp;gt; to use Angular route guards:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
this.canLeave = function() {&lt;br /&gt;
    // ...&lt;br /&gt;
};&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So for example you can make your plugin ask for confirmation if the user tries to leave the page when he has some unsaved data.&lt;br /&gt;
===Module plugins: dynamically determine if a feature is supported===&lt;br /&gt;
In Moodle you can specify if your plugin supports a certain feature, like &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;. If your plugin will always support or not a certain feature, then you can use the &amp;lt;code&amp;gt;supportedfeatures&amp;lt;/code&amp;gt; property in &amp;lt;code&amp;gt;mobile.php&amp;lt;/code&amp;gt; to specify it ([[#Options_only_for_CoreCourseModuleDelegate|see more documentation about this]]). But if you need to calculate it dynamically then you will have to create a function to calculate it. &lt;br /&gt;
&lt;br /&gt;
This can be achieved using the initialisation method (for more info, please see the [[#Initialisation|Initialisation]] section above). The JavaScript returned by your initialisation method will need to define a function named &amp;lt;code&amp;gt;supportsFeature&amp;lt;/code&amp;gt; that will receive the name of the feature:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    supportsFeature: function(featureName) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
};&lt;br /&gt;
result;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Currently the app only uses &amp;lt;code&amp;gt;FEATURE_MOD_ARCHETYPE&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;FEATURE_NO_VIEW_LINK&amp;lt;/code&amp;gt;.&lt;br /&gt;
== Testing ==&lt;br /&gt;
You can also write automated tests for your plugin using Behat, you can read more about it on the [[Acceptance testing for the Moodle App]] page.&lt;br /&gt;
== Upgrading plugins from an older version ==&lt;br /&gt;
If you added mobile support to your plugin for the Ionic 3 version of the app (previous to the 3.9.5 release), you will probably need to make some changes to make it compatible with Ionic 5.&lt;br /&gt;
&lt;br /&gt;
Learn more at the [[Moodle App Plugins Upgrade Guide]].&lt;br /&gt;
==Troubleshooting==&lt;br /&gt;
=== Invalid response received ===&lt;br /&gt;
You might receive this error when using the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive or similar. By default, the app expects all Web Service calls to return an object, if your Web Service returns another type (string, boolean, etc.) then you need to specify it using the &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt; attribute of the directive. For example, if your WS returns a boolean value, then you should specify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{typeExpected: &#039;boolean&#039;}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In a similar way, if your Web Service returns &amp;lt;code&amp;gt;null&amp;lt;/code&amp;gt; you need to tell the app not to expect any result using &amp;lt;code&amp;gt;preSets&amp;lt;/code&amp;gt;:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[preSets]=&amp;quot;{responseExpected: false}&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Values of &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; aren&#039;t sent to my WS ===&lt;br /&gt;
Some directives allow you to specify a form id or name to send the data from the form to a certain WS. These directives look for HTML inputs to retrieve the data to send. However, &amp;lt;code&amp;gt;ion-radio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ion-checkbox&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ion-select&amp;lt;/code&amp;gt; don&#039;t use HTML inputs, they simulate them, so the directive isn&#039;t going to find their data and so it won&#039;t be sent to the Web Service.&lt;br /&gt;
&lt;br /&gt;
There are 2 workarounds to fix this problem.&lt;br /&gt;
==== Sending the data manually ====&lt;br /&gt;
The first solution is to send the missing params manually using the &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt; property. We will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input value in a variable, and this variable will be passed to the parameters. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a template like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group name=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-list radio-group [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/ion-list&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;myws&amp;quot; [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, responses: responses}&amp;quot; form=&amp;quot;myform&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mycomponent.save&#039; | translate }}&lt;br /&gt;
&amp;lt;/ion-button&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Basically, you need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to the affected element (in this case, the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt;). You can put whatever name you want as the value, we used &amp;quot;responses&amp;quot;. With this, every time the user selects a radio button the value will be stored in a variable called &amp;quot;responses&amp;quot;. Then, in the button we are passing this variable to the parameters of the Web Service.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute has priority over &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;, so if you have an input with &amp;lt;code&amp;gt;name=&amp;quot;responses&amp;quot;&amp;lt;/code&amp;gt; it will override what you&#039;re manually passing to &amp;lt;code&amp;gt;params&amp;lt;/code&amp;gt;.&lt;br /&gt;
==== Using a hidden input ====&lt;br /&gt;
Since the directive is looking for HTML inputs, you need to add one with the value to send to the server. You can use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to synchronise your radio/checkbox/select with the new hidden input. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too.&lt;br /&gt;
&lt;br /&gt;
For example, if you have a radio button like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div radio-group name=&amp;quot;responses&amp;quot; [(ngModel)]=&amp;quot;responses&amp;quot;&amp;gt; &lt;br /&gt;
    &amp;lt;ion-item&amp;gt;&lt;br /&gt;
        &amp;lt;ion-label&amp;gt;First value&amp;lt;/ion-label&amp;gt;&lt;br /&gt;
        &amp;lt;ion-radio value=&amp;quot;1&amp;quot;&amp;gt;&amp;lt;/ion-radio&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-input type=&amp;quot;hidden&amp;quot; [ngModel]=&amp;quot;responses&amp;quot; name=&amp;quot;responses&amp;quot;&amp;gt;&amp;lt;/ion-input&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
In the example above, we&#039;re using a variable called &amp;quot;responses&amp;quot; to synchronise the data between the &amp;lt;code&amp;gt;radio-group&amp;lt;/code&amp;gt; and the hidden input. You can use whatever name you want.&lt;br /&gt;
=== I can&#039;t return an object or array in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; ===&lt;br /&gt;
If you try to return an object or an array in any field inside &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, the Web Service call will fail with the following error:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Scalar type expected, array or object received&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Each field in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; must be a string, number or boolean; it cannot be an object or array. To make it work, you need to encode your object or array into a JSON string:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($data)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app will automatically parse this JSON and convert it back into an array or object.&lt;br /&gt;
==Examples==&lt;br /&gt;
===Accepting dynamic names in a Web Service===&lt;br /&gt;
We want to display a form where the names of the fields are dynamic, like it happens in quiz. This data will be sent to a new Web Service that we have created.&lt;br /&gt;
&lt;br /&gt;
The first issue we find is that the Web Service needs to define the names of the parameters received, but in this case they&#039;re dynamic. The solution is to accept an array of objects with name and value. So in the &amp;lt;code&amp;gt;_parameters()&amp;lt;/code&amp;gt; function of our new Web Service, we will add this parameter:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;data&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
     new external_single_structure(&lt;br /&gt;
        [&lt;br /&gt;
            &#039;name&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data name&#039;),&lt;br /&gt;
            &#039;value&#039; =&amp;gt; new external_value(PARAM_RAW, &#039;data value&#039;),&lt;br /&gt;
        ]&lt;br /&gt;
    ),&lt;br /&gt;
    &#039;The data to be saved&#039;, VALUE_DEFAULT, []&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now we need to adapt our form to send the data as the Web Service requires it. In our template, we have a button with the &amp;lt;code&amp;gt;core-site-plugins-call-ws&amp;lt;/code&amp;gt; directive that will send the form data to our Web Service. To make this work we will have to pass the parameters manually, without using the &amp;lt;code&amp;gt;form&amp;lt;/code&amp;gt; attribute, because we need to format the data before it is sent.&lt;br /&gt;
&lt;br /&gt;
Since we will send the parameters manually and we want it all to be sent in the same array, we will use &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to store the input data into a variable that we&#039;ll call &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt;, but you can use the name you want. This variable will be an object that will hold the input data with the format &amp;quot;name-&amp;gt;value&amp;quot;. For example, if I have an input with name &amp;quot;a1&amp;quot; and value &amp;quot;My answer&amp;quot;, the data object will be:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
{a1: &#039;My answer&#039;}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
So we need to add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to all the inputs whose values need to be sent to the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; WS param. Please notice that &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; requires the element to have a name, so if you add &amp;lt;code&amp;gt;ngModel&amp;lt;/code&amp;gt; to a certain element you need to add a name too. For example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-input name=&amp;quot;&amp;lt;% name %&amp;gt;&amp;quot; [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.data[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see, we&#039;re using &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; to store the data. We do it like this because we&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the form, setting the values the user has already stored. If you don&#039;t need to initialise the form, then you can use the &amp;lt;code&amp;gt;dataObject&amp;lt;/code&amp;gt; variable, an empty object that the mobile app creates for you:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
[(ngModel)]=&amp;quot;dataObject[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The app has a function that allows you to convert this data object into an array like the one the WS expects: &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt;. So in our button we&#039;ll use this function to format the data before it&#039;s sent:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;html+ng2&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;ion-button expand=&amp;quot;block&amp;quot; type=&amp;quot;submit&amp;quot; core-site-plugins-call-ws name=&amp;quot;my_ws_name&amp;quot;&lt;br /&gt;
    [params]=&amp;quot;{id: &amp;lt;% id %&amp;gt;, data: CoreUtilsProvider.objectToArrayOfObjects(CONTENT_OTHERDATA.data, &#039;name&#039;, &#039;value&#039;)}&amp;quot;&lt;br /&gt;
    successMessage&lt;br /&gt;
    refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As you can see in the example above, we&#039;re specifying that the keys of the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object need to be stored in a property called &amp;quot;name&amp;quot;, and the values need to be stored in a property called &amp;quot;value&amp;quot;. If your Web Service expects different names you need to change the parameters of the &amp;lt;code&amp;gt;objectToArrayOfObjects&amp;lt;/code&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
If you open your plugin now in the app it will display an error in the JavaScript console. The reason is that the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; variable doesn&#039;t exist inside &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt;. As it is explained in previous sections, &amp;lt;code&amp;gt;CONTENT_OTHERDATA&amp;lt;/code&amp;gt; holds the data that you return in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; for your method. We&#039;ll use &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt; to initialise the values to be displayed in the form.&lt;br /&gt;
&lt;br /&gt;
If the user hasn&#039;t answered the form yet, we can initialise the &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; object as an empty object. Please remember that we cannot return arrays or objects in &amp;lt;code&amp;gt;otherdata&amp;lt;/code&amp;gt;, so we&#039;ll return a JSON string.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; &#039;{}&#039;],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
With the code above, the form will always be empty when the user opens it. But now we want to check if the user has already answered the form and fill the form with the previous values. We will do it like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
$userdata = get_user_responses(); // It will held the data in a format name-&amp;gt;value. Example: [&#039;a1&#039; =&amp;gt; &#039;My value&#039;].&lt;br /&gt;
&lt;br /&gt;
// ...&lt;br /&gt;
&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; [&#039;data&#039; =&amp;gt; json_encode($userdata)],&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now the user will be able to see previous values when the form is opened, and clicking the button will send the data to our Web Service in array format.&lt;br /&gt;
==Moodle plugins with mobile support==&lt;br /&gt;
* Group choice: [https://moodle.org/plugins/mod_choicegroup Moodle plugins directory entry] and [https://github.com/ndunand/moodle-mod_choicegroup code in github].&lt;br /&gt;
* Custom certificate: [https://moodle.org/plugins/mod_customcert Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_customcert code in github].&lt;br /&gt;
* Gapfill question type: [https://moodle.org/plugins/qtype_gapfill Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_gapfill in github].&lt;br /&gt;
* Wordselect question type: [https://moodle.org/plugins/qtype_wordselect Moodle plugins directory entry] and [https://github.com/marcusgreen/moodle-qtype_wordselect in github].&lt;br /&gt;
* RegExp question type: [https://moodle.org/plugins/qtype_regexp Moodle plugins directory entry] and [https://github.com/rezeau/moodle-qtype_regexp in github].&lt;br /&gt;
* Certificate: [https://moodle.org/plugins/mod_certificate Moodle plugins directory entry] and [https://github.com/markn86/moodle-mod_certificate in github].&lt;br /&gt;
* Attendance [https://moodle.org/plugins/mod_attendance Moodle plugins directory entry] and [https://github.com/danmarsden/moodle-mod_attendance in github].&lt;br /&gt;
* ForumNG (unfinished support) [https://moodle.org/plugins/mod_forumng Moodle plugins directory entry] and [https://github.com/moodleou/moodle-mod_forumng in github].&lt;br /&gt;
* News block [https://github.com/moodleou/moodle-block_news in github].&lt;br /&gt;
* H5P activity module [https://moodle.org/plugins/mod_hvp Moodle plugins directory entry] and [https://github.com/h5p/h5p-moodle-plugin in github].&lt;br /&gt;
See the complete list in [https://moodle.org/plugins/browse.php?list=award&amp;amp;id=6 the plugins database] (it may contain some outdated plugins).&lt;br /&gt;
=== Mobile app support award ===&lt;br /&gt;
If you want your plugin to be awarded in the plugins directory and marked as supporting the mobile app, please feel encouraged to contact us via email at [mailto:mobile@moodle.com mobile@moodle.com].&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to include a link to your plugin page and the location of its code repository.&lt;br /&gt;
&lt;br /&gt;
See [https://moodle.org/plugins/?q=award:mobile-app the list of awarded plugins] in the plugins directory.&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
[[Category:Moodle App Ionic 5]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Release_Process&amp;diff=62051</id>
		<title>Moodle App Release Process</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_App_Release_Process&amp;diff=62051"/>
		<updated>2022-04-25T07:51:08Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: /* The following days */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Two weeks before (Code freeze) ==&lt;br /&gt;
{| class=&amp;quot;table table-striped table-bordered&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;width:20px&amp;quot; | #&lt;br /&gt;
! Task&lt;br /&gt;
! style=&amp;quot;width:12%&amp;quot; | Responsibility&lt;br /&gt;
|-&lt;br /&gt;
| 1.&lt;br /&gt;
| Create an issue in the tracker for the release, like: MOBILE-1248&lt;br /&gt;
| Integration Lead&lt;br /&gt;
|-&lt;br /&gt;
| 2.&lt;br /&gt;
| Force an update of the [https://moodle.org/plugins/view.php?id=997 local_moodlemobileapp] plugin (as release candidate) with new strings in moodle.org/plugins (only for Moodle version 2.6). &lt;br /&gt;
| Dev&lt;br /&gt;
|-&lt;br /&gt;
| 3.&lt;br /&gt;
| Ask someone from sites or community team to review the new English strings. &lt;br /&gt;
| Community or Sites team&lt;br /&gt;
|-&lt;br /&gt;
| 4.&lt;br /&gt;
| Announce in the moodletranslation forums the new strings available: https://lang.moodle.org/mod/forum/view.php?id=5. This will allow translators to add the new strings during the days prior to the release.&lt;br /&gt;
| Integration Lead&lt;br /&gt;
|-&lt;br /&gt;
| 5.&lt;br /&gt;
| Add the release notes in the release issue created (search for the [https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20labels%20%3D%20release_notes release_notes tag]). Ask someone from the documentation team or Martin to review the release notes.&lt;br /&gt;
| Integration Lead&lt;br /&gt;
|-&lt;br /&gt;
| 6.&lt;br /&gt;
| Contact the marketing team announcing the new release and highlights.&lt;br /&gt;
| Integration Lead&lt;br /&gt;
|-&lt;br /&gt;
| 7.&lt;br /&gt;
| Add new QA tests to the “Testing cases” document. New QA tests should be labeled with [https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20resolution%20in%20(Unresolved%2C%20Fixed)%20AND%20labels%20%3D%20qa_test_required%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC qa_test_required]. Remove that label once are added to the document.&lt;br /&gt;
| Tester&lt;br /&gt;
|-&lt;br /&gt;
| 8.&lt;br /&gt;
| Run npm audit to ensure all the dependencies are OK.&lt;br /&gt;
| Developer&lt;br /&gt;
|-&lt;br /&gt;
| 9.&lt;br /&gt;
| Freeze Cordova plugins and Javascript libraries versions (node modules) in the integration branch.&lt;br /&gt;
| Developer&lt;br /&gt;
|-&lt;br /&gt;
| 10.&lt;br /&gt;
| &#039;&#039;&#039;Start testing&#039;&#039;&#039;&lt;br /&gt;
| Tester&lt;br /&gt;
|}&lt;br /&gt;
== The release day ==&lt;br /&gt;
{| class=&amp;quot;table table-striped table-bordered&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;width:20px&amp;quot; | #&lt;br /&gt;
! Task&lt;br /&gt;
! style=&amp;quot;width:12%&amp;quot; | Responsibility&lt;br /&gt;
|-&lt;br /&gt;
| 1. &lt;br /&gt;
| Fix the version name in integration/config.xml, integration/package.json and integration:src/config.json (remove the -dev).&lt;br /&gt;
| Integration Lead&lt;br /&gt;
|-&lt;br /&gt;
| 2.&lt;br /&gt;
| Integrate the [https://github.com/moodlehq/moodleapp/compare/master...integration integration branch onto the master one]. &lt;br /&gt;
| Integration Lead&lt;br /&gt;
|-&lt;br /&gt;
| 3.&lt;br /&gt;
| Do some testing with the production builds before sending the application to the stores (overall testing to see that nothing is broken because of the branch merge done in step 2):&lt;br /&gt;
* Correct definitive version name displayed (and commit)&lt;br /&gt;
* AOT compiling worked&lt;br /&gt;
* Language strings synchronised from lang.moodle.net&lt;br /&gt;
* New language packs added&lt;br /&gt;
| All the team&lt;br /&gt;
|-&lt;br /&gt;
| 4.&lt;br /&gt;
| Send the applications to the stores for review. &lt;br /&gt;
| Integration Lead&lt;br /&gt;
|-&lt;br /&gt;
| 5.&lt;br /&gt;
| Create a TAG/Release in github ([https://github.com/moodlehq/moodleapp/releases moodlehq/moodleapp]) with the version number.&lt;br /&gt;
| Integration Lead&lt;br /&gt;
|-&lt;br /&gt;
| 6.&lt;br /&gt;
| Mark the issue and the [https://tracker.moodle.org/projects/MOBILE?selectedItem=com.atlassian.jira.jira-projects-plugin:release-page version] as released in the tracker.&lt;br /&gt;
| Integration Lead&lt;br /&gt;
|-&lt;br /&gt;
| 7.&lt;br /&gt;
| Update release notes [[Moodle_Mobile_Release_Notes]] .&lt;br /&gt;
| Integration Lead&lt;br /&gt;
|}&lt;br /&gt;
== The following days ==&lt;br /&gt;
{| class=&amp;quot;table table-striped table-bordered&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;width:20px&amp;quot; | #&lt;br /&gt;
! Task&lt;br /&gt;
! style=&amp;quot;width:12%&amp;quot; | Responsibility&lt;br /&gt;
|-&lt;br /&gt;
| 1.&lt;br /&gt;
| Social media announcements (Forum and Twitter).&lt;br /&gt;
| All the team &amp;amp; Marketing team &lt;br /&gt;
|-&lt;br /&gt;
| 2.&lt;br /&gt;
| Post in moodle.org/news.&lt;br /&gt;
| Team Lead&lt;br /&gt;
|-&lt;br /&gt;
| 3.&lt;br /&gt;
| Review the users and developers documentation (check that everything is in order). Review the [https://tracker.moodle.org/issues/?jql=project%20%3D%20MOBILE%20AND%20labels%20in%20%28docs_required%2C%20dev_docs_required%29 docs_required and dev_docs_required_tags]. Review the [https://docs.moodle.org/en/Moodle_Mobile_features Mobile features wiki documentation].&lt;br /&gt;
| All the team&lt;br /&gt;
|-&lt;br /&gt;
| 4.&lt;br /&gt;
| Delete the integration and desktop branches and create it again based on master.&lt;br /&gt;
| Team Lead&lt;br /&gt;
|-&lt;br /&gt;
| 5.&lt;br /&gt;
| Bump version numbers in the following files in the integration branch: config.xml, src/config.json (appending a -dev to the versionname field to indicate that is a development version), package.json (including mac-&amp;gt;bundleVersion) and desktop/assets/windows/AppXManifest.xml.&lt;br /&gt;
| Integration Lead&lt;br /&gt;
|-&lt;br /&gt;
| 6.&lt;br /&gt;
| Unfreeze Cordova plugins and Javascript libraries versions (node modules).&lt;br /&gt;
| Developer&lt;br /&gt;
|-&lt;br /&gt;
| 7.&lt;br /&gt;
| Check that the [https://cloud.docker.com/u/moodlehq/repository/docker/moodlehq/moodleapp/general Docker image] for the new version was successfully built.&lt;br /&gt;
| Integration Lead&lt;br /&gt;
|-&lt;br /&gt;
| 8.&lt;br /&gt;
| Update of the [https://moodle.org/plugins/view.php?id=997 local_moodlemobileapp] plugin (as final release) in moodle.org/plugins.&lt;br /&gt;
| Developer&lt;br /&gt;
|}&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Moodle App Release Notes]]&lt;br /&gt;
[[Category:Processes]]&lt;br /&gt;
[[Category:Mobile]]&lt;br /&gt;
{{DISPLAYTITLE:Moodle app release process}}&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Developing_a_plugin_tutorial&amp;diff=61936</id>
		<title>Moodle Mobile 1 Developing a plugin tutorial</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Developing_a_plugin_tutorial&amp;diff=61936"/>
		<updated>2022-04-07T12:57:18Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: Remove legacy links&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile 1}}&lt;br /&gt;
== Introduction ==&lt;br /&gt;
This tutorial describes all the steps needed for developing a plugin for the MoodleMobile app.&lt;br /&gt;
&lt;br /&gt;
In this tutorial we make the following assumptions:&lt;br /&gt;
* You have a medium/good skills/understand of Javascript, PHP and Moodle architecture&lt;br /&gt;
* You know what a Web Service is and how it works in Moodle&lt;br /&gt;
* The plugin you are going to develop requires a Web Service in your Moodle installation to work&lt;br /&gt;
&#039;&#039;&#039;Notice that neither the moodle plugin code and the moodle mobile plugin code are suitable for production environments&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
There is a second part of this tutorial where we extend the current WebServices and create a new &amp;quot;forums&amp;quot; plugin, see [[Moodle Mobile Developing a plugin tutorial part 2]]&lt;br /&gt;
== Plugin spec ==&lt;br /&gt;
We want to develop a &amp;quot;grades&amp;quot; plugin that will display the current user grades for all the gradable activities in a course the user is enroll in.&lt;br /&gt;
&lt;br /&gt;
A screenshot of the desired result:&lt;br /&gt;
&lt;br /&gt;
[[File:MoodleMobileTutorial01.png]]&lt;br /&gt;
&lt;br /&gt;
As you can see there is a new menu called &amp;quot;Grades&amp;quot; that displays a page in the middle panel showing all the course activities. When we click in an activity, the right panel shows the grade / maximum grade for the current user.&lt;br /&gt;
&lt;br /&gt;
In order to achieve this we will have to develop:&lt;br /&gt;
* A plugin for Moodle containing a new Web Service that returns grades and..&lt;br /&gt;
* A MoodleMobile plugin&lt;br /&gt;
== Steps ==&lt;br /&gt;
=== Set up your development environment ===&lt;br /&gt;
Clone or fork the official git repository [https://github.com/moodlehq/moodlemobile] in a local directory.&lt;br /&gt;
&lt;br /&gt;
Set up your development browser, see [[Moodle_Mobile]], I mainly use the Google Chrome browser.&lt;br /&gt;
=== Set up your Moodle installation ===&lt;br /&gt;
Enable debugging, disable caches, etc... the typical settings for developing.&lt;br /&gt;
&lt;br /&gt;
Enable the Web Services and also the protocols you are going to use for testing and developing (REST is mandatory). See [[Web Services]] documentation.&lt;br /&gt;
&lt;br /&gt;
Enable the Mobile Service&lt;br /&gt;
&lt;br /&gt;
Enable the moodle/webservice:createtoken capability for the Authenticated User Role at System Context&lt;br /&gt;
=== Develop the Moodle local/mod/block/... plugin ===&lt;br /&gt;
You can find the full source code here:&lt;br /&gt;
https://github.com/cvaconsulting/moodle-local_custommm&lt;br /&gt;
&lt;br /&gt;
In this case we&#039;ve developed a local plugin that contains a Web Service that returns grades as described here: https://tracker.moodle.org/browse/MDL-30085&lt;br /&gt;
&lt;br /&gt;
Notice that my version has some differences with the previous mentioned one. I have fixed some small bugs and changed some parameters.&lt;br /&gt;
=== Test your Web Services using a demo client ===&lt;br /&gt;
Before continuing you must test that your Web Service works, the local plugin contains a demo REST client, see:&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodle-local_custommm/blob/master/client/client.php&lt;br /&gt;
=== Create your custom Service ===&lt;br /&gt;
A service is a set of functions for an specific purpose. Since Moodle 2.1 there is a pre-built service that contains all the functions needed for the official mobile app.&lt;br /&gt;
&lt;br /&gt;
This service cannot be edited for adding new functions so we don&#039;t have a simple way for make available our new Web Service function to the mobile app.&lt;br /&gt;
&lt;br /&gt;
The only way for fix this is creating a new Service in Moodle, and configuring our app for pointing to this new service.&lt;br /&gt;
&lt;br /&gt;
There are two ways of creating a Service in Moodle:&lt;br /&gt;
* Using a db/services.php in your plugin as mentioned here: [[External services description]]&lt;br /&gt;
&lt;br /&gt;
* Creating the service in your Moodle installation using the forms in Admin &amp;gt; Plugins &amp;gt; Web Services &amp;gt; External Services&lt;br /&gt;
In this case we&#039;ve created the service using the Moodle forms because it gives more flexibility in some aspects, there is a big impediment, due to https://tracker.moodle.org/browse/MDL-29807 in order to add a shortname for the new Service, we have to edit manually the Moodle database for adding the shortname in the dbprefix_external_services table&lt;br /&gt;
&lt;br /&gt;
[[File:MoodleMobileTutorial02.png]]&lt;br /&gt;
&lt;br /&gt;
When creating, the flats &amp;quot;Can download files&amp;quot; and &amp;quot;Enabled&amp;quot; must be checked.&lt;br /&gt;
=== Set up your MoodleMobile client ===&lt;br /&gt;
The most important change you have to do is edit the config.json file and change the wsservice parameter in order to point to the shortname of the Service you created before.&lt;br /&gt;
&lt;br /&gt;
You have to add also the name of the plugin you are developing at the final of the plugins parameter:&lt;br /&gt;
&lt;br /&gt;
&amp;quot;plugins&amp;quot; : [&amp;quot;notifications&amp;quot;, &amp;quot;upload&amp;quot;, &amp;quot;contents&amp;quot;, &amp;quot;participants&amp;quot;, &amp;quot;addcontact&amp;quot;, &amp;quot;addnote&amp;quot;, &amp;quot;sendmessage&amp;quot;, &amp;quot;grades&amp;quot;],&lt;br /&gt;
&lt;br /&gt;
Notice that, for avoid errors, this last change should be done once the file plugins/grades/main.js file exists&lt;br /&gt;
=== Develop the MoodleMobile plugin ===&lt;br /&gt;
The full source code can be found here:&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-grades&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-grades/blob/master/lang/en.json - Contains the language strings, the string &amp;quot;pluginPLUGINname&amp;quot; is mandatory&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-grades/blob/master/icon.png - The plugin icon&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-grades/blob/master/activities.html - The template for the activities list (center panel)&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-grades/blob/master/activitygrade.html - The template for showing the grade for an activity (right panel)&lt;br /&gt;
&lt;br /&gt;
https://github.com/cvaconsulting/moodlemobile-grades/blob/master/main.js - The plugin&#039;s main code&lt;br /&gt;
&lt;br /&gt;
Bellow, I summarize the most important parts of the code (always parts of the main.js file):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
var templates = [&lt;br /&gt;
    &amp;quot;root/externallib/text!root/plugins/grades/activities.html&amp;quot;,&lt;br /&gt;
    &amp;quot;root/externallib/text!root/plugins/grades/activitygrade.html&amp;quot;,&lt;br /&gt;
    &amp;quot;root/externallib/text!root/plugins/grades/lang/en.json&amp;quot;&lt;br /&gt;
];&lt;br /&gt;
&lt;br /&gt;
define(templates,function (activities, activityGrade, langStrings) {&lt;br /&gt;
    var plugin = {&lt;br /&gt;
        settings: {&lt;br /&gt;
            name: &amp;quot;grades&amp;quot;,&lt;br /&gt;
            type: &amp;quot;course&amp;quot;,&lt;br /&gt;
            menuURL: &amp;quot;#course/grades/&amp;quot;,&lt;br /&gt;
            lang: {&lt;br /&gt;
                component: &amp;quot;local_custommm&amp;quot;,&lt;br /&gt;
				strings: langStrings&lt;br /&gt;
            }&lt;br /&gt;
        },&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Notice that we required three files, the two templates (.html) and the .json file that contains the lang strings.&lt;br /&gt;
&lt;br /&gt;
In the plugin.settings structure, we define the lant attribute that contains two very important attributes:&lt;br /&gt;
* Component: It must refer to the Moodle component linked to this plugin&lt;br /&gt;
* strings: The lang strings in .json format (plain text). As you can see, with this line we are populating the plugin.settings.lang.strings var withe the contents of the lang/en.json files&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        viewActivities: function(courseId) {&lt;br /&gt;
&lt;br /&gt;
            MM.panels.showLoading(&#039;center&#039;);&lt;br /&gt;
&lt;br /&gt;
            if (MM.deviceType == &amp;quot;tablet&amp;quot;) {&lt;br /&gt;
                MM.panels.html(&#039;right&#039;, &#039;&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            var data = {&lt;br /&gt;
            &amp;quot;options[0][name]&amp;quot; : &amp;quot;&amp;quot;,&lt;br /&gt;
            &amp;quot;options[0][value]&amp;quot; : &amp;quot;&amp;quot;&lt;br /&gt;
            };            &lt;br /&gt;
            data.courseid = courseId;&lt;br /&gt;
            &lt;br /&gt;
            MM.moodleWSCall(&#039;core_course_get_contents&#039;, data, function(contents) {&lt;br /&gt;
                var course = MM.db.get(&amp;quot;courses&amp;quot;, MM.config.current_site.id + &amp;quot;-&amp;quot; + courseId);&lt;br /&gt;
&lt;br /&gt;
                var tpl = {&lt;br /&gt;
                    sections: contents,&lt;br /&gt;
                    course: course.toJSON() // Convert a model to a plain javascript object.&lt;br /&gt;
                }&lt;br /&gt;
                var html = MM.tpl.render(MM.plugins.grades.templates.activities.html, tpl);&lt;br /&gt;
                MM.panels.show(&amp;quot;center&amp;quot;, html);&lt;br /&gt;
            });&lt;br /&gt;
        },&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The viewActivities is the function that displays in the center panel the list of activities inside a course, as you can see the code is pretty simple. &lt;br /&gt;
* We call to the core_course_get_contents Web Service that returns all the activities and resources inside a course&lt;br /&gt;
* We render the template that will iterate over the sections and activities displaying the links to specifics activities&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;javascript&amp;quot;&amp;gt;&lt;br /&gt;
         viewActivityGrade: function(activityName, courseId, modName, cmId) {&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
            if (MM.deviceType == &amp;quot;tablet&amp;quot;) {&lt;br /&gt;
                MM.panels.showLoading(&#039;right&#039;);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            var data = {&lt;br /&gt;
                &amp;quot;grades[courseid]&amp;quot; : courseId,&lt;br /&gt;
                &amp;quot;grades[component]&amp;quot; : &amp;quot;mod_&amp;quot; + modName,&lt;br /&gt;
                &amp;quot;grades[cmid]&amp;quot; : cmId,&lt;br /&gt;
                &amp;quot;grades[userids][0]&amp;quot; : MM.config.current_site.userid&lt;br /&gt;
            };            &lt;br /&gt;
            &lt;br /&gt;
            MM.moodleWSCall(&#039;local_custommm_get_grades&#039;, data, function(contents) {&lt;br /&gt;
                var course = MM.db.get(&amp;quot;courses&amp;quot;, MM.config.current_site.id + &amp;quot;-&amp;quot; + courseId);&lt;br /&gt;
                &lt;br /&gt;
                var grade= MM.lang.s(&amp;quot;nogrades&amp;quot;, &amp;quot;grades&amp;quot;);&lt;br /&gt;
                &lt;br /&gt;
                if(contents.items[0]) {&lt;br /&gt;
                    grade = contents.items[0][&amp;quot;grades&amp;quot;][0][&amp;quot;str_long_grade&amp;quot;];&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                var tpl = {&lt;br /&gt;
                    activityName: activityName,&lt;br /&gt;
                    sections: contents,&lt;br /&gt;
                    finalGrade: grade,&lt;br /&gt;
                    course: course.toJSON(), // Convert a model to a plain javascript object.&lt;br /&gt;
                    items: contents&lt;br /&gt;
                }&lt;br /&gt;
                var html = MM.tpl.render(MM.plugins.grades.templates.activityGrade.html, tpl);&lt;br /&gt;
                MM.panels.show(&amp;quot;right&amp;quot;, html);&lt;br /&gt;
                if (MM.deviceType == &amp;quot;tablet&amp;quot; &amp;amp;&amp;amp; contents.length &amp;gt; 0) {&lt;br /&gt;
                    // First section.&lt;br /&gt;
                    MM.plugins.contents.viewCourseContentsSection(courseId, 0);&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
        },&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This function gets and displays the grade for the selected activity in the centered panel.&lt;br /&gt;
&lt;br /&gt;
We just call to the Web Service we developed and parse the result, if nothing is returned we display a generic message indicating that there is no grades information for the activity.&lt;br /&gt;
If a grades full structure is returned, we return the first grade for the first grade items (as Moodle usually does) that corresponds to the user grade for the activity&lt;br /&gt;
&lt;br /&gt;
Notice that:&lt;br /&gt;
* Some activities, like workshop, returns two different grades items (teacher and peer grades). The example doesn&#039;t implement any logic for this case.&lt;br /&gt;
* Some activities may return outcomes. The example doesn&#039;t implement any logic for this case.&lt;br /&gt;
* The rest grade information (grade min, grade max, feedback) is also covered by this example.&lt;br /&gt;
=== Deliver your plugin ===&lt;br /&gt;
Android: Package your plugin using &#039;&#039;PhoneGap build&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
iOs: Further instructions soon&lt;br /&gt;
== See also ==&lt;br /&gt;
[[Moodle Mobile Developing a plugin tutorial part 2]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Customization&amp;diff=61935</id>
		<title>Moodle Mobile 1 Customization</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_1_Customization&amp;diff=61935"/>
		<updated>2022-04-07T12:55:50Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: Remove legacy links&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile 1}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
In this document are described the general steps for building your custom Mobile app.&lt;br /&gt;
&lt;br /&gt;
You can find a step by step guide of how to create a custom version of the app here: http://es.slideshare.net/juanleyva/creating-a-custom-moodle-mobile-app-moodle-moot-spain-2014&lt;br /&gt;
== Creating your custom application ==&lt;br /&gt;
Remember that if you only want to change the global styles of the app, you can use an extra CSS file in your Moodle installation without needing to recompile the app.&lt;br /&gt;
=== Basic custom settings ===&lt;br /&gt;
In your Moodle installation you can specify a extra CSS file in the Mobile settings section&lt;br /&gt;
&lt;br /&gt;
You can edit the config.json file for:&lt;br /&gt;
- Adding a default language&lt;br /&gt;
- Add some presets in the login screen (url and username) pointing to your installation&lt;br /&gt;
=== Development environment ===&lt;br /&gt;
See [[#Testing_and_developing]]&lt;br /&gt;
&lt;br /&gt;
It is recommended that you have a local web server (app and moodle installation under localhost or the same local domain).&lt;br /&gt;
&lt;br /&gt;
You will need:&lt;br /&gt;
* A Moodle installation with Web Services (REST) enabled - see [[Web services]] documentation&lt;br /&gt;
:* Administration -&amp;gt; Plugins -&amp;gt; Web Services -&amp;gt; External Services -&amp;gt; Enable mobile web service&lt;br /&gt;
&lt;br /&gt;
:* Administration -&amp;gt; Plugins -&amp;gt; Web Services -&amp;gt; Protocols -&amp;gt; Enable REST&lt;br /&gt;
&lt;br /&gt;
:* Administration -&amp;gt; Users -&amp;gt; Permission -&amp;gt; Define roles -&amp;gt; Edit the Authenticated user role -&amp;gt; Allow webservice/rest:use &lt;br /&gt;
* A local copy of the Mobile HTML5 App with the Debugging options Enabled (https://github.com/moodlehq/moodlemobile)&lt;br /&gt;
=== Web Services Development ===&lt;br /&gt;
In most cases your plugin/s will need Web Services to get data from your Moodle installation. It is recommended that you first develop the Web Services that you will need (see [[Web Services]]).&lt;br /&gt;
* Create a local plugin in Moodle for adding the extra Web Services you need and also your plugin&#039;s lang files (Local WS plugin template: https://moodle.org/plugins/view.php?plugin=local_wstemplate)&lt;br /&gt;
&lt;br /&gt;
* Create a new External Service in your Moodle installation, adding a services.php file in your new local plugin or creating manually the service in your Moodle installation.(Plugins -&amp;gt; Web Services) See [[External services description]] for more info. Remember the key name of the service, because you will need it later.&lt;br /&gt;
=== Moodle Mobile Plugin Development ===&lt;br /&gt;
See [[Moodle Mobile 1 Plugins Development]] and [[Moodle Mobile 1 Developing a plugin tutorial]]&lt;br /&gt;
&lt;br /&gt;
For developing the plugin you need:&lt;br /&gt;
* Edit the /config.json file to:&lt;br /&gt;
:* Add the extra plugins you implement&lt;br /&gt;
:* Indicate the WS service name you are going to use in your Moodle installation (Remember, that you need to create a WS Service indicating the functions available for your custom app)&lt;br /&gt;
:* Add your custom default lang file (es, ca, fr ...) (the language file must exists in the app&#039;s lang/ folder)&lt;br /&gt;
:* Change the app login form presets&lt;br /&gt;
* Add your own css files, custom images, etc..&lt;br /&gt;
=== Building ===&lt;br /&gt;
There are three options for building the app, aka compile it to different devices:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Phonegap Build&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Phonegap Build&#039;&#039; is an online service that let you to build the app automatically for different platforms. The main advantage is that simplify a lot the process of building.&lt;br /&gt;
&lt;br /&gt;
You just put your customizations in a github account and Phonegaps automatically build the app for different platforms pulling your github code.&lt;br /&gt;
&lt;br /&gt;
Notice that the free version is for open repositories, but you can use the non-free version for private repositories.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Phonegap CLI&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You can build also using the Phonegap CLI interface, it requires to install the different Platform SDK / tools but the build process is simpler.&lt;br /&gt;
&lt;br /&gt;
Once installed the environment, you need to deploy in your project/www folder the app code&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Platform SDK&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Building the app for Android]] or [[Moodle Mobile Building the app for iOS]] (These articles may be obsolete)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Automatic build system&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Automatic Building System]] for some instructions for building an automatic building system initially for Android.&lt;br /&gt;
== Testing and developing ==&lt;br /&gt;
=== Chromium or Google Chrome browser ===&lt;br /&gt;
See [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
=== Ripple ===&lt;br /&gt;
Requirements: Google Chrome browser + Ripple mobile environment emulator plugin (http://ripple.tinyhippos.com/)&lt;br /&gt;
&lt;br /&gt;
You must run Google Chrome in Unsafe mode adding this param:&lt;br /&gt;
 --allow-file-access-from-files&lt;br /&gt;
IMPORTANT: I strongly recommend you create a new link or application launch called &amp;quot;Google Unsafe&amp;quot; and use it only for testing the app.&lt;br /&gt;
 &amp;quot;Path to chrome\chrome.exe&amp;quot; --allow-file-access-from-files&lt;br /&gt;
Open the index.html file in the Google Chrome unsafe and click on the Ripple icon to activate the emulator.&lt;br /&gt;
&lt;br /&gt;
Once opened in the Ripple settings block, change Cross Domain Proxy to Disabled.&lt;br /&gt;
&lt;br /&gt;
Please note that some functionalities (camera, audio recording, contact) will not work in the emulator.&lt;br /&gt;
=== Platform SDK ===&lt;br /&gt;
Install the Android or iPhone SDK.&lt;br /&gt;
=== Debugging styles or running a remote Console ===&lt;br /&gt;
You can use Safari or Google Chrome developer tools for debugging (console) and changing the CSS styles in live:&lt;br /&gt;
&lt;br /&gt;
https://developer.chrome.com/devtools/docs/remote-debugging&lt;br /&gt;
&lt;br /&gt;
http://phonegap-tips.com/articles/debugging-ios-phonegap-apps-with-safaris-web-inspector.html&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Local web server ===&lt;br /&gt;
If you deploy the html files in a server under the same domain that the Moodle you are going to connect to you can test the application without emulator or changing the Security settings of your browser.&lt;br /&gt;
&lt;br /&gt;
In other words&lt;br /&gt;
&lt;br /&gt;
Your Moodle at http://myhost.com/moodle24&lt;br /&gt;
&lt;br /&gt;
Your app at http://myhost.com/moodlemobile/&lt;br /&gt;
&lt;br /&gt;
or&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Your Moodle at http://localhost/moodle24&lt;br /&gt;
&lt;br /&gt;
Your app at http://localhost/moodlemobile/&lt;br /&gt;
&lt;br /&gt;
Note that the mobile - related modules will not work.&lt;br /&gt;
== See also ==&lt;br /&gt;
[http://es.slideshare.net/juanleyva/creating-a-custom-moodle-mobile-app-moodle-moot-spain-2014 Creating a custom Moodle Mobile app - MoodleMoot Spain 2014]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Customization&amp;diff=61934</id>
		<title>Moodle Mobile 2 (Ionic 1) Customization</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_2_(Ionic_1)_Customization&amp;diff=61934"/>
		<updated>2022-04-07T12:54:12Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: /* Building */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile 2 (Ionic 1)}}&lt;br /&gt;
IMPORTANT: This documentation will be obsolete in May 2018, please [https://moodle.org/mod/forum/discuss.php?d=360412 read this announcement].&lt;br /&gt;
== Overview ==&lt;br /&gt;
Moodle Mobile can be customized in different ways:&lt;br /&gt;
* Changing the app appearance for your site.&lt;br /&gt;
* Creating a set of remote add-ons in order to add additional functionalities to the app.&lt;br /&gt;
* Creating a custom application (with your custom name and icon) based on the Moodle Mobile code.&lt;br /&gt;
* Configuring how the app should work via Moodle site settings.&lt;br /&gt;
With the first two options you are not required to compile or build your custom application, with the third one you are required to compile and publish your custom app in the stores.&lt;br /&gt;
== Changing the app appearance for your site ==&lt;br /&gt;
The Mobile app can retrieve your custom styles from your Moodle site. Since is an HTML5 app, you can apply safely CSS3 styles.&lt;br /&gt;
&lt;br /&gt;
In your Moodle installation go to Plugins / Web services / Mobile and enter in the mobilecssurl field a valid URL pointing to a CSS file containing your custom styles (theme).&lt;br /&gt;
&lt;br /&gt;
The CSS should be placed inside your Moodle installation (in your custom theme or inside a local plugin)&lt;br /&gt;
&lt;br /&gt;
Once the user is logged in the app, there is a periodical process that retrieves your remote CSS files for applying your custom styles into the app.&lt;br /&gt;
&lt;br /&gt;
Notice that on the first time a user opens the app, he will see the default &amp;quot;orange&amp;quot; style. Your custom styles will be applied once the user has added a site in the app.&lt;br /&gt;
&lt;br /&gt;
Notice also that styles comes from a site, there is no way for changing the initial Add site / Manage account pages styles.&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Themes]] for additional information.&lt;br /&gt;
== Remote add-ons  ==&lt;br /&gt;
Remote add-ons are available since Moodle 3.1 and Moodle Mobile 3.1.&lt;br /&gt;
&lt;br /&gt;
A Moodle Mobile remote add-on is the mobile app version of a Moodle plugin that will be loaded when a user accesses the site on the app.&lt;br /&gt;
&lt;br /&gt;
Moodle Mobile remote add-ons are stored within a Moodle plugin and are downloaded and lazy-loaded by the Mobile app in every site that uses them. &lt;br /&gt;
&lt;br /&gt;
Remote add-ons are the best choice if you just want to support a Moodle plugin in the Mobile app without having to create and maintain a custom version.&lt;br /&gt;
&lt;br /&gt;
See [[Moodle Mobile Remote add-ons]] for complete information.&lt;br /&gt;
== Creating a custom application ==&lt;br /&gt;
See [[Moodle Mobile custom apps]] or [https://moodle.com/mobileapp/ The Branded Moodle Mobile App service] by Moodle HQ&lt;br /&gt;
=== Building ===&lt;br /&gt;
There are two options for building the app, aka compile it to different devices:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Phonegap Build&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Phonegap Build is an online service that lets you build the app automatically for different platforms. The main advantage is that it simplifies a lot the process of building.&lt;br /&gt;
&lt;br /&gt;
You just put your www/ folder in a github account and Phonegaps automatically builds the app for different platforms pulling your github code.&lt;br /&gt;
&lt;br /&gt;
Notice that the free version is for open repositories, but you can use the non-free version for private repositories.&lt;br /&gt;
&lt;br /&gt;
In order to build your app using Phonegap Build you need to place a special config.xml file into the www/ folder, see the config.xml file for Moodle Mobile 2 (Please, note that this file is different from the one created by Ionic in the app root folder). Please follow the tutorial mentioned in [[Moodle Mobile custom apps]] to view all the steps required for building the app using Phonegap Build service.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Ionic CLI&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
You can also build the app using the Ionic CLI tools, see: http://ionicframework.com/docs/cli/run.html&lt;br /&gt;
== Configuring how the app should work via Moodle site settings ==&lt;br /&gt;
The app can be configured via Moodle site settingss. You can disable features, rename strings, add new elements to the main menu or change how log out works in the app. This requires Moodle 3.3 or the [[https://docs.moodle.org/en/Moodle_Mobile_additional_features Moodle Mobile additional features plugin]].&lt;br /&gt;
&lt;br /&gt;
Please refer to the Configuring the app from your site section in this page: https://docs.moodle.org/en/Moodle_Mobile_guide_for_admins&lt;br /&gt;
== Testing and developing ==&lt;br /&gt;
=== Chromium or Google Chrome browser ===&lt;br /&gt;
See [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
== Moodle Desktop ==&lt;br /&gt;
Refer to [[Moodle Desktop]]&lt;br /&gt;
== See also ==&lt;br /&gt;
[https://moodle.com/mobileapp/ Branded Moodle Mobile app service by Moodle HQ]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_custom_apps&amp;diff=61933</id>
		<title>Moodle Mobile custom apps</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_custom_apps&amp;diff=61933"/>
		<updated>2022-04-07T12:52:20Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile 2 (Ionic 1)}}&lt;br /&gt;
== Overview ==&lt;br /&gt;
You will need a custom app if you want:&lt;br /&gt;
* Force the users to log in to your site URL&lt;br /&gt;
* Custom corporate image (name, app icon, theme, store entry)&lt;br /&gt;
* Add additional features or static pages&lt;br /&gt;
* Remove or change existing features&lt;br /&gt;
* Custom translation of the app&lt;br /&gt;
* Use your own notifications infrastructure&lt;br /&gt;
== Requirements ==&lt;br /&gt;
* Code editor (Sublime, WebStorm, Eclipse, Netbeans...)&lt;br /&gt;
* Github account (http://github.com/)&lt;br /&gt;
* Github desktop app (http://windows.github.com/)&lt;br /&gt;
* PhonegapBuild account&lt;br /&gt;
* Chromium browser as described here: [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
== Steps for creating your Custom App ==&lt;br /&gt;
=== Setting up your computer ===&lt;br /&gt;
Follow these guides:&lt;br /&gt;
* [[Setting up your development environment for Moodle Mobile 2]]&lt;br /&gt;
* [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
=== Fork the app ===&lt;br /&gt;
Use the Fork tool in:&lt;br /&gt;
https://github.com/moodlehq/moodleapp (You must be logged with your Github user!)&lt;br /&gt;
&lt;br /&gt;
[[File:forkCustomApp.png]]&lt;br /&gt;
=== Clone the repo in your desktop ===&lt;br /&gt;
You can use your favourite GIT client (or GitHub Desktop clients)&lt;br /&gt;
&lt;br /&gt;
[[File:clone.png]]&lt;br /&gt;
=== Create a new branch ===&lt;br /&gt;
Having the code in a separate branch in the forked repository will make very easy to maintain our customizations using git tools (merge, rebase)&lt;br /&gt;
&lt;br /&gt;
[[File:branch.png]]&lt;br /&gt;
=== Edit the app settings file ===&lt;br /&gt;
&#039;&#039;&#039;/config.xml&#039;&#039;&#039;&lt;br /&gt;
* Change the widget id value (instead com.moodle.moodlemobile your own id)&lt;br /&gt;
* Change the app name and description&lt;br /&gt;
&#039;&#039;&#039;/www/config.json&#039;&#039;&#039;&lt;br /&gt;
* Change versionname&lt;br /&gt;
* Add a pre-fixed URL: Adding a field like “siteurl”: “http://mysite.abc”&lt;br /&gt;
* Change gcmpn (if you are using your custom Google Push infrastructure)&lt;br /&gt;
* Change the appid (required for making Push notifications work)&lt;br /&gt;
* Remove demo sites (“demo_sites”: “”)&lt;br /&gt;
=== Edit another app files ===&lt;br /&gt;
&#039;&#039;&#039;/www/core/component/settings/lang/x.json (x means all the languages)&#039;&#039;&#039;&lt;br /&gt;
* Change the “appname” string to match your custom app name (Not necessary in latest version Moodle Mobile 3.1.3), you can change it in config.json&lt;br /&gt;
&#039;&#039;&#039;/www/errorreport.js&#039;&#039;&#039;&lt;br /&gt;
* Change: var reportUrl to point to your custom error reporting system (if you want to use your custom one)&lt;br /&gt;
* Same for appVersion&lt;br /&gt;
=== Replace the app icons, splashscreen ===&lt;br /&gt;
* Replace the resources/ icon.png and splash images&lt;br /&gt;
* Replace also the www/img/moodle.png with an image matching your logo&lt;br /&gt;
* &#039;&#039;&#039;In a console:&#039;&#039;&#039;&lt;br /&gt;
** cd mycustomappdir/&lt;br /&gt;
** ionic resources&lt;br /&gt;
This will create your custom icon and splash images in all the required sizes&lt;br /&gt;
&lt;br /&gt;
More information about Ionic’s image generation: http://ionicframework.com/docs/cli/icon-splashscreen.html&lt;br /&gt;
=== Add your custom styles ===&lt;br /&gt;
* Add your custom styles in scss/app.scss&lt;br /&gt;
* Sample theme available at: [[Moodle Mobile Themes]]&lt;br /&gt;
[[File:addCustomStyle.png]]&lt;br /&gt;
== Building your Custom App ==&lt;br /&gt;
There are two options for building the app:&lt;br /&gt;
* Ionic CLI&lt;br /&gt;
** Local building (require installation of Android and iOs SDK and emulators)&lt;br /&gt;
** See http://ionicframework.com/docs/cli/run.html&lt;br /&gt;
* Phonegap Build&lt;br /&gt;
** Remote building (cloud service)&lt;br /&gt;
** This is the recommended way because it doesn’t require to install all the SDK, emulators (or having to buy a Mac for developing)&lt;br /&gt;
=== Building with Phonegap Build ===&lt;br /&gt;
==== GitHub ====&lt;br /&gt;
* First of all, you need to create a new repository in GitHub. This is because Phonegap Build requires a different directory structure than Ionic CLI and we need a branch per platform.&lt;br /&gt;
* In your forked repository, run &#039;&#039;gulp&#039;&#039; so the build files are updated.&lt;br /&gt;
* Copy all the code inside the www/ in your forked repository directory to this new repo/branch.&lt;br /&gt;
* Also copy the /resources directory from the ionic project to a new /resources directory in the phonegap build repository&lt;br /&gt;
* You also have to place the /resources/icon.png and /resources/splash.png you used to create your resource files in the root folder in the phonegap build repository&lt;br /&gt;
* Use this repository as an example: &amp;lt;s&amp;gt;github.com/moodlehq/moodlemobile-phonegapbuild/tree/learn-moodle&amp;lt;/s&amp;gt;&lt;br /&gt;
* In the Phonegap Build repo root folder you need to add a special config.xml file (different to the Ionic one), use [https://raw.githubusercontent.com/moodlehq/moodlemobile-phonegapbuild/learn-moodle/config.xml this file] as a template. Please, replace there the app name, versions, etc.&lt;br /&gt;
* Then, in the Phonegap build repo you need two create two additional branches based on master: android and ios&lt;br /&gt;
* In the android and ios branch you need to apply these changes:&lt;br /&gt;
Disable debugging in the app:&lt;br /&gt;
&lt;br /&gt;
[[file:disableDebug.png]]&lt;br /&gt;
&lt;br /&gt;
Add android or ios base class (so the app use native style widgets):&lt;br /&gt;
&lt;br /&gt;
[[file:addBaseClass.png]]&lt;br /&gt;
* The rest of the commits can be safely omitted:&lt;br /&gt;
** Add reporting library&lt;br /&gt;
** Add official tag&lt;br /&gt;
==== Phonegap ====&lt;br /&gt;
* Create an account in &#039;&#039;PhoneGap&#039;&#039;.&lt;br /&gt;
* For iOS you need to create certificates/provisioning files.&lt;br /&gt;
* For Android, you can download and install the .apk for testing, for production follow the previous guide.&lt;br /&gt;
* In order to build, add your repository https link and also the branch in the Phonegap Build settings (you will have to do two different builds: one for the android branch and another one for the ios branch)&lt;br /&gt;
* In order to update your app for phonegap build you have to (this will apply all the specific android/ios commits to your master branch):&lt;br /&gt;
** Upload new versions of the app to the www/ directory and then execute the commands:&lt;br /&gt;
** git rebase master android&lt;br /&gt;
** git push -f&lt;br /&gt;
** git rebase master ios&lt;br /&gt;
** git push -f&lt;br /&gt;
=== Remove existing features ===&lt;br /&gt;
* You can only remove add-ons.&lt;br /&gt;
* In order to disable an add-on you need to delete the folder.&lt;br /&gt;
* Add-ons are located in the www/addons folder.&lt;br /&gt;
* In case you want to remove a particular functionality of an add-on you will need to edit the files inside the addon. Please notice that removing a state is not enough for a feature to be disabled, you also need to remove all the links to that state to prevent errors.&lt;br /&gt;
* Then run gulp or ionic serve in the project root folder so the build files are created.&lt;br /&gt;
=== Change the app strings ===&lt;br /&gt;
* Language strings are located in:&lt;br /&gt;
** www/core/lang folder (a different file for each language)&lt;br /&gt;
** www/core/components/COMPONENT_NAME/lang folder&lt;br /&gt;
** www/addons/ADDON_NAME/lang folder&lt;br /&gt;
* Language files are .json files, you can edit it using a text editor. Please, use jsonlint.com to validate that the new language file is correct.&lt;br /&gt;
* Then run &#039;&#039;&#039;gulp&#039;&#039;&#039; or &#039;&#039;&#039;ionic serve&#039;&#039;&#039; in the project root folder so the build files are created&lt;br /&gt;
=== Change the initial screen (add site) ===&lt;br /&gt;
* To make the app use a fixed site URL you need to add a new setting in the config.json file: “siteurl”: “http://yourmoodle.com”&lt;br /&gt;
* Then run &#039;&#039;&#039;gulp&#039;&#039;&#039; or &#039;&#039;&#039;ionic serve&#039;&#039;&#039; in the project root folder so the build files are created.&lt;br /&gt;
You can see an example from our Learn Moodle app (that is a custom version of Moodle Mobile) here: https://github.com/jleyva/moodleapp/blob/learn-moodle/www/config.json&lt;br /&gt;
=== Keep your custom version up2date ===&lt;br /&gt;
* Sync your fork: https://help.github.com/articles/syncing-a-fork/&lt;br /&gt;
* Rebase your changes (and resolve conflicts):&lt;br /&gt;
** git rebase master yourcustombranch&lt;br /&gt;
* Upgrade your version number&lt;br /&gt;
* Commit your changes&lt;br /&gt;
* Apply the changes in your Phonegap Build specific repository&lt;br /&gt;
* Rebuild with Phonegap build for both android and ios branches&lt;br /&gt;
* Publish in the store&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Building_the_app_for_Android&amp;diff=61932</id>
		<title>Moodle Mobile Building the app for Android</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Building_the_app_for_Android&amp;diff=61932"/>
		<updated>2022-04-07T12:50:28Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle App (Ionic 3)}}&lt;br /&gt;
Remember that you can also build the app using an external system called Phonegap Build, see [[Moodle_Mobile_Customization#Building]]&lt;br /&gt;
{{obsolete}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Installing the Android SDK ==&lt;br /&gt;
Building the app for Android is easy once you have successfully installed all the required software.&lt;br /&gt;
&lt;br /&gt;
Since we are using Phonegap as framework for creating the app, the documentation you have to follow is just in this link plus additional steps for installing plugins:&lt;br /&gt;
&amp;lt;s&amp;gt;docs.phonegap.com/en/guide_getting-started_android_index.md.html#Getting%20Started%20with%20Android&amp;lt;/s&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Notice the following:&lt;br /&gt;
* You must read carefully the document and not use white spaces in some names and ids. (It&#039;s adviced in the document)&lt;br /&gt;
* You can optionally not use Eclipse and instead use adb command line or cordova script tools&lt;br /&gt;
* In the &amp;quot;4. Setup New Project&amp;quot; The package_name is the app id that your app will have in the Android Market (Play Store), so it must be unique. If you are going to develop for the official app you have to use com.moodle.moodlemobile, if you are going to develop a custom app, you will have to add your own unique id.&lt;br /&gt;
* If you want to connect a Device to your Eclipse and you are using Windows, you must install the Android USB Drivers, see http://developer.android.com/tools/extras/oem-usb.html#InstallingDriver&lt;br /&gt;
== Add the Mobile app HTML5 files ==&lt;br /&gt;
Go to your project folder, assets/www and add just there all the files from https://github.com/moodlehq/moodlemobile&lt;br /&gt;
== Installing additional Phonegap plugins ==&lt;br /&gt;
The Android app requires two additional Phonegap plugins to be installed:&lt;br /&gt;
=== WebIntent ===&lt;br /&gt;
For opening files according their mime type: https://github.com/phonegap/phonegap-plugins/tree/DEPRECATED/Android/WebIntent&lt;br /&gt;
&lt;br /&gt;
For installing the plugin follow this instructions: https://github.com/phonegap/phonegap-plugins/blob/DEPRECATED/Android/WebIntent/README.md#adding-the-plugin-to-your-project&lt;br /&gt;
== Build, compile, test your app ==&lt;br /&gt;
We suggest you to use Eclipse tools for testing, deploying the app&lt;br /&gt;
&lt;br /&gt;
In any case, you can always use the Cordova command line tools.&lt;br /&gt;
&lt;br /&gt;
The cli tools are in your project folder, directory cordova&lt;br /&gt;
== Signing your app ==&lt;br /&gt;
&#039;&#039;The Android system requires that all installed applications be digitally signed with a certificate whose private key is held by the application&#039;s developer. The Android system uses the certificate as a means of identifying the author of an application and establishing trust relationships between applications. The certificate is not used to control which applications the user can install. The certificate does not need to be signed by a certificate authority: it is perfectly allowable, and typical, for Android applications to use self-signed certificates.&#039;&#039; See: http://developer.android.com/tools/publishing/app-signing.html&lt;br /&gt;
&lt;br /&gt;
And also for Eclipse: http://developer.android.com/tools/publishing/app-signing.html#ExportWizard&lt;br /&gt;
== Uploading your custom app to the Google Play Store ==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==  Build the official Moodle Mobile app on a Mac ==&lt;br /&gt;
# Install SDK&lt;br /&gt;
#*http://developer.android.com/sdk/index.html#download - it will install Eclipse / ADT plugin / Android SDK&lt;br /&gt;
# Add the SDK tools/platform-tools folders to your PATH&lt;br /&gt;
# Install NodeJs&lt;br /&gt;
#*http://nodejs.org/ &lt;br /&gt;
# Install cordova-cli&lt;br /&gt;
#* &#039;&#039;sudo npm install -g cordova&#039;&#039; - it will install cordova. Note that on my machine I have some warnings about the android bootstrap process being skipped but all the following steps seem to work ok anyway...&lt;br /&gt;
# Create a Android project&lt;br /&gt;
#*&#039;&#039;/usr/local/lib/node_modules/cordova/bin/cordova create ~/Apps/moodlemobile_android_cordova com.moodle.moodlemobile MoodleMobileBeta&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cd ~/Apps/moodlemobile_android_cordova&#039;&#039;&lt;br /&gt;
#* &#039;&#039;sudo /usr/local/lib/node_modules/cordova/bin/cordova platform add android&#039;&#039;&lt;br /&gt;
#* &#039;&#039;sudo /usr/local/lib/node_modules/cordova/bin/cordova build&#039;&#039; - not necessary, just check the build works.&lt;br /&gt;
# In Finder change the permission on ~/Apps/moodlemobile_android_cordova folder: Read + Write to everyone&lt;br /&gt;
# Open eclipse, add a new Android project from existing source pointing to /Users/jerome/Apps/moodlemobile_android_cordova/platforms/android. Test you can build the default Cordova app on your phone.&lt;br /&gt;
# Copy the Moodle mobile app into assests/www folder&lt;br /&gt;
#* &#039;&#039;git clone git://github.com/moodlehq/moodlemobile.git ./moodlemobile&#039;&#039;&lt;br /&gt;
#* &#039;&#039;mv platforms/android/assets/www/ platforms/android/assets/www_old/&#039;&#039;&lt;br /&gt;
#* &#039;&#039;mv ./moodlemobile/ platforms/android/assets/www/&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/android/assets/www_old/cordova.js platforms/android/assets/www/&#039;&#039;&lt;br /&gt;
# Add the cordova plugins&lt;br /&gt;
#* &#039;&#039;git clone git://github.com/alunny/ChildBrowser.git ./ChildBrowser&#039;&#039; - we are now going to install cordova compatible plugin with the plugin install command line.&lt;br /&gt;
#* &#039;&#039;sudo /usr/local/lib/node_modules/cordova/bin/cordova plugin add ChildBrowser/&#039;&#039;&lt;br /&gt;
# Add manually phonegap plugins&lt;br /&gt;
#* &#039;&#039;git clone git://github.com/phonegap/phonegap-plugins.git ./phonegap-plugins&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp phonegap-plugins/Android/WebIntent/webintent.js platforms/android/assets/www/&#039;&#039;&lt;br /&gt;
#* &#039;&#039;mkdir platforms/android/src/com/borismus&#039;&#039;&lt;br /&gt;
#* &#039;&#039;mkdir platforms/android/src/com/borismus/webintent&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp phonegap-plugins/Android/WebIntent/WebIntent.java platforms/android/src/com/borismus/webintent&#039;&#039;&lt;br /&gt;
#* In Eclipse, add a package to src folder. The value should be com.borismus.webintent&lt;br /&gt;
#* In your res/xml/config.xml file, add &#039;&#039;&amp;lt;plugin name=&amp;quot;WebIntent&amp;quot; value=&amp;quot;com.borismus.webintent.WebIntent&amp;quot; /&amp;gt;&#039;&#039; where you define the plugins.&lt;br /&gt;
# copy the icons&lt;br /&gt;
#* &#039;&#039;cp platforms/android/assets/www/img/icon/android/icon-36-ldpi.png platforms/android/res/drawable-ldpi/icon.png&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/android/assets/www/img/icon/android/icon-48-mdpi.png platforms/android/res/drawable-mdpi/icon.png&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/android/assets/www/img/icon/android/icon-72-hdpi.png platforms/android/res/drawable-hdpi/icon.png&#039;&#039;&lt;br /&gt;
#* &#039;&#039;cp platforms/android/assets/www/img/icon/android/icon-96-xhdpi.png platforms/android/res/drawable-xhdpi/icon.png&#039;&#039;&lt;br /&gt;
# Edit the AndroidManifest.xml&lt;br /&gt;
#* increment &#039;&#039;android:versionCode=&amp;quot;359&amp;quot; android:versionName=&amp;quot;1.2.0.2&amp;quot;&#039;&#039;&lt;br /&gt;
#* keep the following permissions: &#039;&#039;INTERNET, RECORD_AUDIO, RECORD_VIDEO, READ_CONTACTS, WRITE_CONTACTS, WRITE_EXTERNAL_STORAGE, ACCESS_NETWORK_STATE&#039;&#039;&lt;br /&gt;
#* &#039;&#039;android:debuggable=&amp;quot;false&amp;quot;&#039;&#039; - this reduce the apk size file from 7M to less than 1M!&lt;br /&gt;
# Edit &#039;&#039;res/values/strings.xml&#039;&#039; and change the app name for &amp;quot;Moodle mobile&amp;quot; - this app name appears on your Android phone dashboard. The Google Play Store app name comes from the Google Play publisher console.&lt;br /&gt;
# Clean up the app to reduce apk file size&lt;br /&gt;
#* &#039;&#039;rm -rf platforms/android/assets/www_old/&#039;&#039;&lt;br /&gt;
#* &#039;&#039;rm -rf platforms/android/assets/www/img/splash&#039;&#039;&lt;br /&gt;
#* &#039;&#039;rm -rf platforms/android/assets/www/img/icon&#039;&#039;&lt;br /&gt;
# In Eclispe, build and test the app.&lt;br /&gt;
# In Eclipse, File &amp;gt; Export the app with the Moodle HQ Google Play keystore. Then upload the APK to Google Play.&lt;br /&gt;
=== Warnings ===&lt;br /&gt;
* if you previously installed a different platform then you&#039;ll get an error when adding the cordova plugin. It&#039;s advised to install both platforms at the same time. Then add the cordova plugin(s), then copy the app content into www. Don&#039;t forget cordova.js and plugin js/files. Then you can manually install phonegap plugins.&lt;br /&gt;
= See also =&lt;br /&gt;
* [[Moodle Mobile Building the app for iOS]]&lt;br /&gt;
* [[Moodle Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Testing_with_Phonegap_Developer_app&amp;diff=61931</id>
		<title>Moodle Mobile Testing with Phonegap Developer app</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Testing_with_Phonegap_Developer_app&amp;diff=61931"/>
		<updated>2022-04-07T12:47:29Z</updated>

		<summary type="html">&lt;p&gt;Pferre22: Remove legacy links&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile 1}}&lt;br /&gt;
 &#039;&#039;Phonegap Build&#039;&#039; service was discontinued October, 1 2020.&lt;br /&gt;
The &#039;&#039;Phonegap Developer app&#039;&#039; is a tool that allow developing locally and see the changes instantly on your mobile device.&lt;br /&gt;
&lt;br /&gt;
It&#039;s not a tool that can be used for developing and testing all the features of the Moodle Mobile app since it doesn&#039;t support all the plugins used by the app and also, the FileTransfer plugin are not fully supported.&lt;br /&gt;
&lt;br /&gt;
It doesn&#039;t support also connection to different sites using XHR (Ajax) request using POST or HEAD methods.&lt;br /&gt;
&lt;br /&gt;
The app is suitable for testing mainly CSS / Design issues in different devices (iOs, Android and WindowsPhone 8)&lt;br /&gt;
&lt;br /&gt;
If you want to test the Moodle Mobile app in order to polish design issues, please follow this instructions:&lt;br /&gt;
* Install the Phonegap Developer app in your iOs, Android or WindowsPhone 8 device&lt;br /&gt;
* Install node.js http://nodejs.org/&lt;br /&gt;
* In a terminal/console: sudo npm install -g phonegap&lt;br /&gt;
* Terminal/console: cd directory_where_to_create_the_project&lt;br /&gt;
* Terminal/console: phonegap create moodlemobile&lt;br /&gt;
* Terminal/console: cd moodlemobile&lt;br /&gt;
* Delete the contents of the www/ folder&lt;br /&gt;
* Copy the Moodle Mobile app files into the www/ folder&lt;br /&gt;
* In a terminal, cd to the project folder and then: phonegap serve (This will launch a local server, you can stop the server doing a ctrl+c)&lt;br /&gt;
* Open the app in a device, and copy the server Address that the previous command displayed&lt;br /&gt;
* In the Moodle Mobile app Site URL field enter: mmtest (It&#039;s a &amp;quot;fake&amp;quot; test site, remember that you can&#039;t connect to real sites using the Phonegap Developer app)&lt;br /&gt;
Remember that every time you want to use the Phonegap Developer app, you need to open a terminal/console, cd to your phonegap project folder, and run the command:&lt;br /&gt;
 phonegap serve&lt;br /&gt;
If you have problems testing with the app, try to close the app in your Mobile device and stop the Phonegap server (ctrl + c in the terminal)&lt;br /&gt;
[[Category: Mobile]]&lt;/div&gt;</summary>
		<author><name>Pferre22</name></author>
	</entry>
</feed>