<?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=Colin.bernard</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=Colin.bernard"/>
	<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/Special:Contributions/Colin.bernard"/>
	<updated>2026-08-13T16:41:12Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=56340</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=56340"/>
		<updated>2019-08-27T16:16:08Z</updated>

		<summary type="html">&lt;p&gt;Colin.bernard: Period (syntax error) to comma.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile}}&lt;br /&gt;
{{Moodle Mobile 3.5}}&lt;br /&gt;
&lt;br /&gt;
==Before 3.5==&lt;br /&gt;
&lt;br /&gt;
Since Moodle 3.1 Moodle plugins could be supported in the Mobile app, but only by writing an Angular JS/Ionic module, compiling it to a zip, and including that in your plugin. See [[Moodle Mobile Remote add-ons|Remote add-ons]] for details.&lt;br /&gt;
&lt;br /&gt;
In Moodle 3.5 the app switched to a new way to support plugins that was much easier for developers.&lt;br /&gt;
* This new way will allow developers to support plugins using PHP code, templates and Ionic markup (html components).&lt;br /&gt;
* The use of JavaScript is optional (but some type of advanced plugins may require it)&lt;br /&gt;
* Developers won’t need to set up a Mobile development environment, they will be able to test using the latest version of the official app (although setting up a local Mobile environment is recommended for complex plugins).&lt;br /&gt;
&lt;br /&gt;
This means that remote add-ons won’t be necessary anymore, and developers won’t have to learn Ionic 3 / Angular and set up a new mobile development environment to migrate them. Plugins using the old Remote add-ons mechanism will have to be migrated to the new simpler way (following this documentation)&lt;br /&gt;
&lt;br /&gt;
This new way is natively supported in Moodle 3.5. For previous versions you will need to install the Moodle Mobile Additional Features plugin.&lt;br /&gt;
&lt;br /&gt;
==How it works==&lt;br /&gt;
&lt;br /&gt;
The overall idea is to allow Moodle plugins to extend different areas in the app with &#039;&#039;just PHP server side&#039;&#039; code and Ionic 3 markup (custom html elements that are called components) using a set of custom Ionic directives and components.&lt;br /&gt;
&lt;br /&gt;
Developers will have to:&lt;br /&gt;
# Create a db/mobile.php file in their plugins. In this file developers will be able to indicate which areas of the app they want to extend, for example, adding a new option in the main menu, implementing an activity module not supported, 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 (html). The template should use [https://ionicframework.com/docs/components/ Ionic components] so that it looks native (custom html elements) but it can be generated using mustache templates. &lt;br /&gt;
&lt;br /&gt;
Let’s clarify some points:&lt;br /&gt;
&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 tool_mobile_get_content&lt;br /&gt;
* As arguments of your functions you will always receive the userid, some relevant details of the app (app version, current language in the app, etc…) and some specific data depending on the type of plugin (courseid, cmid, …).&lt;br /&gt;
* We provide a list of custom Ionic components and directives (html tags) that will provide dynamic behaviour, like indicating that you are linking a file that can be downloaded, or to allow a transition to new pages into the app calling a specific function in the server, submit form data to the server  etc..&lt;br /&gt;
&lt;br /&gt;
==Types of plugins==&lt;br /&gt;
&lt;br /&gt;
We could classify all the plugins in 3 different types:&lt;br /&gt;
&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
&lt;br /&gt;
[[File:Templates_downloaded_when_requested.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
With this type of plugin, the template of your plugin will be generated and downloaded when the user opens your plugin in the app. This means that your function will receive some context params. For example, if you&#039;re developing a course module plugin you will receive the courseid and the cmid (course module ID). You can see the list of delegates that support this type of plugin in the [[Mobile_support_for_plugins#Delegates|Delegates]] section.&lt;br /&gt;
&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
&lt;br /&gt;
[[File:Templates_downloaded_on_login.png|thumb]]&lt;br /&gt;
&lt;br /&gt;
With this type of plugin, the template for your plugin will be downloaded when the user logins in the app and will be stored in the device. This means that your function will not receive any context params, and you need to return a generic template that will be built with JS data like the ones in the Mobile 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 [[Mobile_support_for_plugins#Delegates|Delegates]] section.&lt;br /&gt;
&lt;br /&gt;
===Pure Javascript plugins===&lt;br /&gt;
&lt;br /&gt;
You can always implement your 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 [[Mobile_support_for_plugins#Delegates|Delegates]] section.&lt;br /&gt;
&lt;br /&gt;
==Step by step example==&lt;br /&gt;
&lt;br /&gt;
In this example, we are going to update an existing plugin ([https://github.com/markn86/moodle-mod_certificate Certificate activity module]) that currently uses a Remote add-on.&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;
&lt;br /&gt;
The example code can be downloaded from here (https://github.com/markn86/moodle-mod_certificate/commit/003fbac0d80fd96baf428255500980bf95a7a0d6)&lt;br /&gt;
&lt;br /&gt;
TIP: Make sure to ([https://docs.moodle.org/35/en/Developer_tools#Purge_all_caches purge all cache]) after making an edit to one of the following files for your changes to be taken into account.&lt;br /&gt;
&lt;br /&gt;
===Step 1. Update the db/mobile.php file===&lt;br /&gt;
In this case, we are updating an existing file but for new plugins, you should create this new file.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&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;/code&amp;gt;&lt;br /&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 chapter in this documentation for all the possible options.&lt;br /&gt;
&lt;br /&gt;
; Method:&lt;br /&gt;
: This is the method in the Moodle \(component)\output\mobile 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;
: These are the functions that need to be downloaded for offline usage. 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 mobile.php 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 userid (and courseid 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 userid and courseid)&lt;br /&gt;
: Prefetching the module will also download all the files returned by the methods in these offline functions (in the &#039;&#039;files&#039;&#039; array).&lt;br /&gt;
: Note: If your functions use additional custom parameters (for example, if you implement multiple pages within a module&#039;s view function by using a &#039;page&#039; parameter in addition to the usual cmid, courseid, userid) then the app will not know which additional parameters to supply. In this case, do not list the function in offlinefunctions; instead, you will need to manually implement a [[#Module_prefetch_handler|module prefetch handler]].&lt;br /&gt;
&lt;br /&gt;
;Lang:&lt;br /&gt;
: &amp;lt;nowiki&amp;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 (e.g. [&#039;cancel&#039;, &#039;moodle&#039;]). If you do this, be warned that in the app you will then need to refer to that string as {{ &#039;plugin.myplugin.cancel&#039; | translate }} (not {{ &#039;plugin.moodle.cancel&#039; | translate }})&amp;lt;/nowiki&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;
&lt;br /&gt;
There are additional attributes supported by the mobile.php list, see “Mobile.php supported options” section below.&lt;br /&gt;
&lt;br /&gt;
===Step 2. Creating the main function===&lt;br /&gt;
&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 classes/output directory, the name of the class must be mobile.&lt;br /&gt;
&lt;br /&gt;
For this example (mod_certificate plugin) the namespace name will be mod_certificate\output.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;File contents: mod/certificate/classes/output/mobile.php&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
namespace mod_certificate\output;&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();&lt;br /&gt;
&lt;br /&gt;
use context_module;&lt;br /&gt;
use mod_certificate_external;&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * Mobile output class for certificate&lt;br /&gt;
 *&lt;br /&gt;
 * @package    mod_certificate&lt;br /&gt;
 * @copyright  2018 Juan Leyva&lt;br /&gt;
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later&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;
     * @param  array $args Arguments from tool_mobile_get_content WS&lt;br /&gt;
     *&lt;br /&gt;
     * @return array       HTML, javascript and otherdata&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;, array(&#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 = array();&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;
        list($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 = array(&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Let’s go through the function code to analyse the different parts.&lt;br /&gt;
&lt;br /&gt;
;Function declaration: &lt;br /&gt;
: The function name is the same as the one used in the mobile.php file (method field). There is only one argument “$args” which is an array containing all the information sent by the mobile app (the courseid, userid, appid, appversionname, appversioncode, applang, appcustomurlscheme…)&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 view.php script would do normally). Then we retrieve the certificate information that’s necessary to display the template.&lt;br /&gt;
&lt;br /&gt;
Finally, we return:&lt;br /&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;
* JavaScript: Empty, because we don’t need any in this case&lt;br /&gt;
* Other data: 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-bind to the template.&lt;br /&gt;
* Files: A list of files that the app should be able to download (for offline usage mostly)&lt;br /&gt;
&lt;br /&gt;
===Step 3. Creating the template for the main function===&lt;br /&gt;
&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 and custom directives and components available in the Mobile app.&lt;br /&gt;
&lt;br /&gt;
All the HTML attributes starting with ion- 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 attributes starting with &#039;&#039;core-&#039;&#039; are custom components of the Mobile app.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;File contents: mod/certificate/templates/mobile_view_page.mustache&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&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;&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;button ion-button block color=&amp;quot;light&amp;quot; core-site-plugins-new-content title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; 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.viewcertificateviews&#039; | translate: {$a: &amp;lt;% numissues %&amp;gt;} }}&lt;br /&gt;
                &amp;lt;/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;button ion-button block core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot; courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; [files]=&amp;quot;[{fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;, filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;, timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;}]&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;/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; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot;&amp;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;/code&amp;gt;&lt;br /&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;&amp;lt;core-course-module-description&amp;lt;/code&amp;gt; that 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;
The following line &amp;lt;code&amp;gt;{{ &#039;plugin.mod_certificate.summaryofattempts&#039; | translate }}&amp;lt;/code&amp;gt; indicates that the Mobile app will translate the &#039;&#039;summaryofattempts&#039;&#039; 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 this format: &lt;br /&gt;
&lt;br /&gt;
“plugin” + plugin identifier (from mobile.php) +  string id (the string must be indicated in the lang field in mobile.php). &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 function “mobile_issues_view” in the component “mod_certificate” passing as arguments the cmid and courseid. The content returned by this function will be displayed in a new page (see Step 4 for 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 in offline.&lt;br /&gt;
&lt;br /&gt;
Finally, just before the ion-list 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 to a Web Service function in the server, in this case we are calling the &#039;&#039;mod_certificate_view_certificate&#039;&#039; 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;
&lt;br /&gt;
&#039;&#039;&#039;Partial file contents: mod/certificate/classes/output/mobile.php&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&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, javascript and otherdata&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;, array(&#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 = array();&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $data = [&lt;br /&gt;
            &#039;issues&#039; =&amp;gt; $issues&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_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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This function for the new page was added just after the mobile_course_view function, the code is quite similar: Capabilities checks, 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;
&#039;&#039;&#039;File contents: mod/certificate/templates/mobile_view_issues.mustache&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&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;/code&amp;gt;&lt;br /&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 &#039;&#039;coreToLocaleString&#039;&#039;). 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;
&lt;br /&gt;
If your plugin uses its own web services, they will also need to be enabled for mobile access in your db/services.php file.&lt;br /&gt;
&lt;br /&gt;
The following line &amp;lt;code&amp;gt;&#039;services&#039;      =&amp;gt; [MOODLE_OFFICIAL_MOBILE_SERVICE, &#039;local_mobile&#039;],&amp;lt;/code&amp;gt; should be included in each webservice definition.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;File contents: mod/certificate/db/services.php&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$functions = [&lt;br /&gt;
&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;/code&amp;gt;&lt;br /&gt;
	&lt;br /&gt;
This extra services definition is the reason why you will need to have the local_mobile plugin installed for Moodle versions 3.4 and lower, so that your Moodle site will have all the additional webservices included to deal with all these mobile access calls. This is explained further in the [https://docs.moodle.org/dev/Mobile_support_for_plugins#Moodle_version_requirements Moodle version requirements section] below.&lt;br /&gt;
&lt;br /&gt;
==Getting started==&lt;br /&gt;
&lt;br /&gt;
The first and most important thing to know is that you don’t need a local mobile environment, you can just use the Chrome or Chromium browser to add mobile support to your plugins!&lt;br /&gt;
&lt;br /&gt;
Open this URL (with Chrome or Chromium browser): https://mobileapp.moodledemo.net/ and you will see a web version of the mobile app completely functional (except for some native features). This URL is updated with the latest integration version of the app.&lt;br /&gt;
&lt;br /&gt;
Please test that your site works correctly in the web version before starting any development.&lt;br /&gt;
&lt;br /&gt;
===Moodle version requirements===&lt;br /&gt;
&lt;br /&gt;
If your Moodle version is lower than 3.5 you will need to install the [https://docs.moodle.org/en/Moodle_Mobile_additional_features Moodle Mobile additional features plugin]. &lt;br /&gt;
&lt;br /&gt;
Please use this development version for now: https://github.com/moodlehq/moodle-local_mobile/commits/MOODLE_31_STABLE (if your Moodle version is 3.2, 3.3 or 3.4) you will have to use the specific branch for your version but applying manually the [https://github.com/moodlehq/moodle-local_mobile/commits/MOODLE_31_STABLE last commit from the 3.1 branch] (the one with number MOBILE-2362).&lt;br /&gt;
&lt;br /&gt;
Also, when installing the Moodle Mobile Additional features plugin you must follow the installation instructions so the service is set up properly.&lt;br /&gt;
&lt;br /&gt;
Remember to update your plugin documentation to reflect that this plugin is mandatory for Mobile support. We don’t recommend to indicate in your plugin version.php a dependency to local_mobile though.&lt;br /&gt;
&lt;br /&gt;
===Development workflow===&lt;br /&gt;
&lt;br /&gt;
First of all, we recommend creating a simple &#039;&#039;mobile.php&#039;&#039; for displaying a new main menu option (even if your plugin won’t be in the main menu, just to verify that you are able to extend the app plugins). Then open the webapp (https://mobileapp.moodledemo.net/) or refresh the browser if it was already open. Check that you can correctly  see the new menu option you included.&lt;br /&gt;
&lt;br /&gt;
Then, develop the main function of the app returning a “Hello world” or basic code (without using templates) to see that everything works together. After adding the classes/output/mobile.php file it is very important to “Purge all caches” to avoid problems with the auto-loading cache.&lt;br /&gt;
&lt;br /&gt;
It is important to remember that:&lt;br /&gt;
* Any change in the mobile.php file will require you to refresh the web app page in the browser (remember to disable the cache in the Chrome developer options).&lt;br /&gt;
* Any change in an existing template or function won’t require to refresh the browser page. In most cases you should just do a PTR (Pull down To Refresh) in the page that displays the view returned by the function. Be aware that PTR will work only when using the “device” emulation in the browser (see following section).&lt;br /&gt;
&lt;br /&gt;
===Testing and debugging===&lt;br /&gt;
&lt;br /&gt;
To learn how to debug with the web version of the app, please read the following documents:&lt;br /&gt;
* [[Moodle Mobile debugging WS requests]] AND&lt;br /&gt;
* [[Moodle Mobile development using Chrome or Chromium]] (please, omit the installation section)&lt;br /&gt;
&lt;br /&gt;
For plugins using the Javascript API you may develop making use of the console.log function to add trace messages in your code that will be displayed in the browser console.&lt;br /&gt;
&lt;br /&gt;
Within the app, make sure to turn on the option: &#039;&#039;&#039;App settings&#039;&#039;&#039; / &#039;&#039;&#039;General&#039;&#039;&#039; / &#039;&#039;&#039;Display debug messages&#039;&#039;&#039;. This means popup errors from the app will show more information.&lt;br /&gt;
&lt;br /&gt;
==Mobile.php supported options==&lt;br /&gt;
&lt;br /&gt;
In the Step by Step section we learned about some of the existing options for handlers configuration. This is the full list of supported options:&lt;br /&gt;
&lt;br /&gt;
===Common options===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;delegate&#039;&#039;&#039; (mandatory): Name of the delegate to register the handler in.&lt;br /&gt;
* &#039;&#039;&#039;method&#039;&#039;&#039; (mandatory): The function to call to retrieve the main page content.&lt;br /&gt;
* &#039;&#039;&#039;init&#039;&#039;&#039; (optional): A function to call to retrieve the initialization JS and the &amp;quot;restrict&amp;quot; to apply to the whole handler. It can also return templates that can be used from the Javascript of the init method or the Javascript of the handler’s method.&lt;br /&gt;
* &#039;&#039;&#039;restricttocurrentuser&#039;&#039;&#039; (optional) Only used if the delegate has a isEnabledForUser function. If true, the handler will only be shown for current user. For more info about displaying the plugin only for certain users, please see [[Mobile_support_for_plugins#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &#039;&#039;&#039;restricttoenrolledcourses&#039;&#039;&#039; (optional): Only used if the delegate has a isEnabledForCourse 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 [[Mobile_support_for_plugins#Display_the_plugin_only_if_certain_conditions_are_met|Display the plugin only if certain conditions are met]].&lt;br /&gt;
* &#039;&#039;&#039;styles&#039;&#039;&#039; (optional): An array with two properties: &#039;&#039;url&#039;&#039; and &#039;&#039;version&#039;&#039;. 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;
* &#039;&#039;&#039;moodlecomponent&#039;&#039;&#039; (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 &#039;&#039;local_whatever&#039;&#039;, but in &amp;quot;moodlecomponent&amp;quot; you can specify that this handler will implement &#039;&#039;format_whatever&#039;&#039; or &#039;&#039;mod_whatever&#039;&#039;. This property was introduced in the version 3.6.1 of the app.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreCourseOptionsDelegate===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;displaydata&#039;&#039;&#039; (mandatory): title, class.&lt;br /&gt;
* &#039;&#039;&#039;priority&#039;&#039;&#039; (optional): Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
&lt;br /&gt;
===Options only for CoreMainMenuDelegate===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;displaydata&#039;&#039;&#039; (mandatory): title, icon, class.&lt;br /&gt;
* &#039;&#039;&#039;priority&#039;&#039;&#039; (optional): Priority of the handler. Higher priority is displayed first. Main Menu plugins are always displayed in the &amp;quot;More&amp;quot; tab, they cannot be displayed as tabs in the bottom bar.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreCourseModuleDelegate===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;displaydata&#039;&#039;&#039; (mandatory): icon, class.&lt;br /&gt;
* &#039;&#039;&#039;offlinefunctions&#039;&#039;&#039;: (optional) List of functions to call when prefetching the module. It can be a get_content method or a WS. You can filter the params received by the WS. By default, WS will receive these params: courseid, cmid, userid. Other valid values that will be added if they are present in the list of params: courseids (it will receive a list with the courses the user is enrolled in), component + &#039;id&#039; (e.g. certificateid).&lt;br /&gt;
* &#039;&#039;&#039;downloadbutton&#039;&#039;&#039;: (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;
* &#039;&#039;&#039;isresource&#039;&#039;&#039;: (optional) Whether the module is a resource or an activity. Only used if there is any offlinefunction. If your module relies on the &amp;quot;contents&amp;quot; field, then it should be true.&lt;br /&gt;
* &#039;&#039;&#039;updatesnames&#039;&#039;&#039;: (optional) Only used if there is any offlinefunction. A Regular Expression to check if there&#039;s any update in the module. It will be compared to the result of &#039;&#039;core_course_check_updates&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;displayopeninbrowser&#039;&#039;&#039;: (optional) Supported from the 3.6 version of the app. 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: this.displayOpenInBrowser = false;&lt;br /&gt;
* &#039;&#039;&#039;displaydescription&#039;&#039;&#039;: (optional) Supported from the 3.6 version of the app. Whether the module should display the &amp;quot;Description&amp;quot; option in the top-right menu. This can be done in JavaScript too: this.displayDescription = false;&lt;br /&gt;
* &#039;&#039;&#039;displayrefresh&#039;&#039;&#039;: (optional) Supported from the 3.6 version of the app. Whether the module should display the &amp;quot;Refresh&amp;quot; option in the top-right menu. This can be done in JavaScript too: this.displayRefresh = false;&lt;br /&gt;
* &#039;&#039;&#039;displayprefetch&#039;&#039;&#039;: (optional) Supported from the 3.6 version of the app. Whether the module should display the download option in the top-right menu. This can be done in JavaScript too: this.displayPrefetch = false;&lt;br /&gt;
* &#039;&#039;&#039;displaysize&#039;&#039;&#039;: (optional) Supported from the 3.6 version of the app. Whether the module should display the downloaded size in the top-right menu. This can be done in JavaScript too: this.displaySize = false;&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreCourseFormatDelegate===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;canviewallsections&#039;&#039;&#039;: (optional) Whether the course format allows seeing all sections in a single page. Defaults to true.&lt;br /&gt;
* &#039;&#039;&#039;displayenabledownload&#039;&#039;&#039;: (optional) Whether the option to enable section/module download should be displayed. Defaults to true.&lt;br /&gt;
* &#039;&#039;&#039;displaysectionselector&#039;&#039;&#039;: (optional) Whether the default section selector should be displayed. Defaults to true.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreUserDelegate===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;displaydata&#039;&#039;&#039; (mandatory): title, icon, class.&lt;br /&gt;
* &#039;&#039;&#039;type&#039;&#039;&#039;: The type of the addon. Values accepted: &#039;newpage&#039; (default) or  &#039;communication&#039;. &lt;br /&gt;
* &#039;&#039;&#039;priority&#039;&#039;&#039; (optional): Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
&lt;br /&gt;
===Options only for CoreSettingsDelegate===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;displaydata&#039;&#039;&#039; (mandatory): title, icon, class.&lt;br /&gt;
* &#039;&#039;&#039;priority&#039;&#039;&#039; (optional): Priority of the handler. Higher priority is displayed first. &lt;br /&gt;
&lt;br /&gt;
===Options only for AddonMessageOutputDelegate===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;displaydata&#039;&#039;&#039; (mandatory): title, icon.&lt;br /&gt;
* &#039;&#039;&#039;priority&#039;&#039;&#039; (optional): Priority of the handler. Higher priority is displayed first.&lt;br /&gt;
&lt;br /&gt;
===Options only for CoreBlockDelegate===&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;displaydata&#039;&#039;&#039; (optional): title, class. If &#039;&#039;title&#039;&#039; is not supplied, it will default to &amp;quot;plugins.block_blockname.pluginname&amp;quot;, where &#039;&#039;blockname&#039;&#039; is the name of the block. If &#039;&#039;class&#039;&#039; is not supplied, it will default to &amp;quot;block_blockname&amp;quot;, where &#039;&#039;blockname&#039;&#039; is the name of the block.&lt;br /&gt;
&lt;br /&gt;
==Delegates==&lt;br /&gt;
&lt;br /&gt;
The delegates can be classified by type of plugin. For more info about type of plugins, please see the See [[Mobile_support_for_plugins#Types_of_plugins|Types of plugins]] section.&lt;br /&gt;
&lt;br /&gt;
===Templates generated and downloaded when the user opens the plugins===&lt;br /&gt;
&lt;br /&gt;
====CoreMainMenuDelegate====&lt;br /&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;
&lt;br /&gt;
====CoreCourseOptionsDelegate====&lt;br /&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;
&lt;br /&gt;
====CoreCourseModuleDelegate====&lt;br /&gt;
&lt;br /&gt;
You must use this delegate for supporting activity modules or resources.&lt;br /&gt;
&lt;br /&gt;
====CoreUserDelegate====&lt;br /&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;
&lt;br /&gt;
====CoreCourseFormatDelegate====&lt;br /&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 CoreCourseFormatDelegate 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.  More information is available on [[Creating mobile course formats]].&lt;br /&gt;
&lt;br /&gt;
====CoreSettingsDelegate====&lt;br /&gt;
&lt;br /&gt;
You must use this delegate to add a new option in the settings page.&lt;br /&gt;
&lt;br /&gt;
====AddonMessageOutputDelegate====&lt;br /&gt;
&lt;br /&gt;
You must use this delegate to support a message output plugin.&lt;br /&gt;
&lt;br /&gt;
====CoreBlockDelegate====&lt;br /&gt;
&lt;br /&gt;
You must use this delegate to support a block. As of Moodle App 3.7.0, blocks are only displayed in Site Home and Dashboard, but they&#039;ll be supported in other places of the app soon (e.g. in the course page).&lt;br /&gt;
&lt;br /&gt;
===Templates downloaded on login and rendered using JS data===&lt;br /&gt;
&lt;br /&gt;
====CoreQuestionDelegate====&lt;br /&gt;
&lt;br /&gt;
You must use this delegate for supporting question types.&lt;br /&gt;
https://docs.moodle.org/dev/Creating_mobile_question_types&lt;br /&gt;
&lt;br /&gt;
====CoreQuestionBehaviourDelegate====&lt;br /&gt;
&lt;br /&gt;
You must use this delegate for supporting question behaviours.&lt;br /&gt;
&lt;br /&gt;
====CoreUserProfileFieldDelegate====&lt;br /&gt;
&lt;br /&gt;
You must use this delegate for supporting user profile fields.&lt;br /&gt;
&lt;br /&gt;
====AddonModQuizAccessRuleDelegate====&lt;br /&gt;
&lt;br /&gt;
You must use this delegate to support a quiz access rule.&lt;br /&gt;
&lt;br /&gt;
====AddonModAssignSubmissionDelegate and AddonModAssignFeedbackDelegate====&lt;br /&gt;
&lt;br /&gt;
You must use these delegates to support assign submission or feedback plugins.&lt;br /&gt;
&lt;br /&gt;
====AddonWorkshopAssessmentStrategyDelegate====&lt;br /&gt;
&lt;br /&gt;
You must use this delegate to support a workshop assessment strategy plugin.&lt;br /&gt;
&lt;br /&gt;
===Pure Javascript plugins===&lt;br /&gt;
&lt;br /&gt;
These delegates require JavaScript to be supported. See [[Mobile_support_for_plugins#Initialization|Initialization]] for more information.&lt;br /&gt;
&lt;br /&gt;
* CoreContentLinksDelegate&lt;br /&gt;
* CoreCourseModulePrefetchDelegate&lt;br /&gt;
* CoreFileUploaderDelegate&lt;br /&gt;
* CorePluginFileDelegate&lt;br /&gt;
&lt;br /&gt;
==Available components and directives==&lt;br /&gt;
&lt;br /&gt;
===Difference between component and directives===&lt;br /&gt;
&lt;br /&gt;
A component (represented as an HTML tag) is used to add custom elements to the app.&lt;br /&gt;
Example of components are: ion-list, ion-item, core-search-box&lt;br /&gt;
&lt;br /&gt;
A directive (represented as an HTML attribute) allows you to extend a piece of HTML with additional information or functionality.&lt;br /&gt;
Example of directives are: core-auto-focus, *ngIf, ng-repeat&lt;br /&gt;
&lt;br /&gt;
The Mobile app uses Angular, Ionic and custom components and directives, for a full reference of:&lt;br /&gt;
* Angular directives, please check: https://angular.io/api?type=directive&lt;br /&gt;
* Ionic components, please check: https://ionicframework.com/docs/&lt;br /&gt;
&lt;br /&gt;
===Custom core components and directives===&lt;br /&gt;
&lt;br /&gt;
These are some useful custom components and directives (only available in the mobile app). Please notice that this isn’t the full list of components and directives of the app, it’s just an extract of the most common ones.&lt;br /&gt;
&lt;br /&gt;
====core-format-text====&lt;br /&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 core-external-content and core-link to all the links and embedded media.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;text&#039;&#039;&#039; (string): The text to format.&lt;br /&gt;
* &#039;&#039;&#039;siteId&#039;&#039;&#039; (string): Optional. Site ID to use. If not defined, current site.&lt;br /&gt;
* &#039;&#039;&#039;component&#039;&#039;&#039; (string): Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &#039;&#039;&#039;componentId&#039;&#039;&#039; (string|number): Optional. ID to use in conjunction with the component.&lt;br /&gt;
* &#039;&#039;&#039;adaptImg&#039;&#039;&#039; (boolean): Optional. Whether to adapt images to screen width. Defaults to true.&lt;br /&gt;
* &#039;&#039;&#039;clean&#039;&#039;&#039; (boolean): Optional. Whether all the HTML tags should be removed. Defaults to false.&lt;br /&gt;
* &#039;&#039;&#039;singleLine&#039;&#039;&#039; (boolean): Optional. Whether new lines should be removed (all text in single line). Only if clean=true. Defaults to false.&lt;br /&gt;
* &#039;&#039;&#039;maxHeight&#039;&#039;&#039; (number): Optional. Max height in pixels to render the content box. It should be 50 at least to make sense. Using this parameter will force display: block to calculate height better. If you want to avoid this use class=&amp;quot;inline&amp;quot; at the same time to use display: inline-block.&lt;br /&gt;
* &#039;&#039;&#039;fullOnClick&#039;&#039;&#039; (boolean): Optional. Whether it should open a new page with the full contents on click. Only if maxHeight is set and the content has been collapsed. Defaults to false.&lt;br /&gt;
* &#039;&#039;&#039;fullTitle&#039;&#039;&#039; (string): Optional. Title to use in full view. Defaults to &amp;quot;Description&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====core-link====&lt;br /&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 core-format-text.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;capture&#039;&#039;&#039; (boolean): Optional, default false. 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;
* &#039;&#039;&#039;inApp&#039;&#039;&#039; (boolean): Optional, default false. True to open in embedded browser, false to open in system browser.&lt;br /&gt;
* &#039;&#039;&#039;autoLogin&#039;&#039;&#039; (string): Optional, default &amp;quot;check&amp;quot;. If the link should be open with auto-login. Accepts the following values:&lt;br /&gt;
** &amp;quot;yes&amp;quot; -&amp;gt; Always auto-login.&lt;br /&gt;
** &amp;quot;no&amp;quot; -&amp;gt; Never auto-login.&lt;br /&gt;
** &amp;quot;check&amp;quot; -&amp;gt; Auto-login only if it points to the current site. Default value.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;code php&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====core-external-content====&lt;br /&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 core-format-text.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;siteId&#039;&#039;&#039; (string): Optional. Site ID to use. If not defined, current site.&lt;br /&gt;
* &#039;&#039;&#039;component&#039;&#039;&#039; (string): Optional. Component to use when downloading embedded files.&lt;br /&gt;
* &#039;&#039;&#039;componentId&#039;&#039;&#039; (string|number): Optional. ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;code php&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====core-user-link====&lt;br /&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;
&lt;br /&gt;
* &#039;&#039;&#039;userId&#039;&#039;&#039; (number): User id to open the profile.&lt;br /&gt;
* &#039;&#039;&#039;courseId&#039;&#039;&#039; (number): Optional. Course id to show the user info related to that course.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;code php&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====core-file====&lt;br /&gt;
&lt;br /&gt;
Component to handle a remote file. It shows the file name, icon (depending on mimetype) and a button to download/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;
&lt;br /&gt;
* file (object): The file. Must have a property &#039;filename&#039; and a &#039;fileurl&#039; or &#039;url&#039;&lt;br /&gt;
* component (string): Optional. Component the file belongs to.&lt;br /&gt;
* componentId (string|number): Optional. ID to use in conjunction with the component.&lt;br /&gt;
* canDelete (boolean): Optional. Whether file can be deleted.&lt;br /&gt;
* alwaysDownload (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;
* canDownload (boolean): Optional. Whether file can be downloaded. Defaults to true.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;core-file [file]=&amp;quot;{fileurl: &#039;&amp;lt;% issue.url %&amp;gt;&#039;, filename: &#039;&amp;lt;% issue.name %&amp;gt;&#039;, timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, filesize: &#039;&amp;lt;% issue.size %&amp;gt;&#039;}&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-file&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====core-download-file====&lt;br /&gt;
&lt;br /&gt;
Directive to allow downloading and open 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 core-file 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;
&lt;br /&gt;
* &#039;&#039;&#039;core-download-file&#039;&#039;&#039; (object): The file to download.&lt;br /&gt;
* &#039;&#039;&#039;component&#039;&#039;&#039; (string): Optional. Component to link the file to.&lt;br /&gt;
* &#039;&#039;&#039;componentId&#039;&#039;&#039; (string|number): Optional. Component ID to use in conjunction with the component.&lt;br /&gt;
&lt;br /&gt;
Example usage: a button to download a file.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button [core-download-file]=&amp;quot;{fileurl: &amp;lt;% issue.url %&amp;gt;, timemodified: &amp;lt;% issue.timemodified %&amp;gt;, filesize: &amp;lt;% issue.size %&amp;gt;}&amp;quot; component=&amp;quot;mod_certificate&amp;quot; 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;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====core-course-download-module-main-file====&lt;br /&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 mod_resource.&lt;br /&gt;
&lt;br /&gt;
This directive must receive either a module or a moduleId. If no files are provided, it will use module.contents.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;module&#039;&#039;&#039; (object): Optional. The module object. Required if module is not supplied.&lt;br /&gt;
* &#039;&#039;&#039;moduleId&#039;&#039;&#039; (number): Optional. The module ID. Required if module is not supplied.&lt;br /&gt;
* &#039;&#039;&#039;courseId&#039;&#039;&#039; (number): The course ID the module belongs to.&lt;br /&gt;
* &#039;&#039;&#039;component&#039;&#039;&#039; (string): Optional. Component to link the file to.&lt;br /&gt;
* &#039;&#039;&#039;componentId&#039;&#039;&#039; (string|number): Optional. Component ID to use in conjunction with the component. If not defined, moduleId.&lt;br /&gt;
* &#039;&#039;&#039;files&#039;&#039;&#039; (object[]): Optional. List of files of the module. If not provided, use module.contents.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button block core-course-download-module-main-file moduleId=&amp;quot;&amp;lt;% cmid %&amp;gt;&amp;quot; courseId=&amp;quot;&amp;lt;% certificate.course %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; [files]=&amp;quot;[{fileurl: &#039;&amp;lt;% issue.fileurl %&amp;gt;&#039;, filename: &#039;&amp;lt;% issue.filename %&amp;gt;&#039;, timemodified: &#039;&amp;lt;% issue.timemodified %&amp;gt;&#039;, mimetype: &#039;&amp;lt;% issue.mimetype %&amp;gt;&#039;}]&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.getcertificate&#039; | translate }}&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====core-navbar-buttons====&lt;br /&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;ion-buttons&amp;gt; found in the header.&lt;br /&gt;
&lt;br /&gt;
You can use the [hidden] 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;code php&amp;gt;&lt;br /&gt;
&amp;lt;core-navbar-buttons end&amp;gt;&lt;br /&gt;
    &amp;lt;button ion-button icon-only (click)=&amp;quot;action()&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-icon name=&amp;quot;funnel&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
    &amp;lt;/button&amp;gt;&lt;br /&gt;
&amp;lt;/core-navbar-buttons&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also use this to add options to the context menu. Example usage:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&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 [priority]=&amp;quot;500&amp;quot; [content]=&amp;quot;&#039;Nice boat&#039;&amp;quot; (action)=&amp;quot;boatFunction()&amp;quot; [iconAction]=&amp;quot;&#039;boat&#039;&amp;quot;&amp;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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that it is not currently possible to remove or modify options from the context menu without using a nasty hack.&lt;br /&gt;
&lt;br /&gt;
===Specific component and directives for plugins===&lt;br /&gt;
&lt;br /&gt;
These are component and directives created specifically for supporting Moodle plugins.&lt;br /&gt;
&lt;br /&gt;
====core-site-plugins-new-content====&lt;br /&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;
&lt;br /&gt;
* &#039;&#039;&#039;component&#039;&#039;&#039; (string): The component of the new content.&lt;br /&gt;
* &#039;&#039;&#039;method&#039;&#039;&#039; (string): The method to get the new content.&lt;br /&gt;
* &#039;&#039;&#039;args&#039;&#039;&#039; (object): The params to get the new content.&lt;br /&gt;
* &#039;&#039;&#039;preSets&#039;&#039;&#039; (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;
* &#039;&#039;&#039;title&#039;&#039;&#039; (string): The title to display with the new content. Only if samePage=false.&lt;br /&gt;
* &#039;&#039;&#039;samePage&#039;&#039;&#039; (boolean): Whether to display the content in same page or open a new one. Defaults to new page.&lt;br /&gt;
* &#039;&#039;&#039;useOtherData&#039;&#039;&#039; (any): Whether to include &#039;&#039;otherdata&#039;&#039; (from the &#039;&#039;get_content&#039;&#039; WS call) in the args for the new &#039;&#039;get_content&#039;&#039; call. If not supplied, no other data will be added. If supplied but empty (&#039;&#039;&#039;null, false or empty string&#039;&#039;&#039;) all the &#039;&#039;otherdata&#039;&#039; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that [useOtherData]=&amp;quot;&amp;quot; is the same as not supplying it, so nothing will be copied.&lt;br /&gt;
* &#039;&#039;&#039;form&#039;&#039;&#039; (string): ID or name to identify a form in the template. The form will be obtained from &#039;&#039;document.forms&#039;&#039;. If supplied and form is found, the form data will be retrieved and sent to the new &#039;&#039;get_content&#039;&#039; WS call. If your form contains an ion-radio, ion-checkbox or ion-select, please see [[Mobile_support_for_plugins#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of ion-radio, ion-checkbox or ion-select aren&#039;t sent to my WS]].&lt;br /&gt;
&lt;br /&gt;
Example usages:&lt;br /&gt;
&lt;br /&gt;
A button to go to a new content page:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button core-site-plugins-new-content title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; 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;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A button to load new content in current page using userid from otherdata:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button core-site-plugins-new-content component=&amp;quot;mod_certificate&amp;quot; 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; 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;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====core-site-plugins-call-ws====&lt;br /&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.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;name&#039;&#039;&#039; (string): The name of the WS to call.&lt;br /&gt;
* &#039;&#039;&#039;params&#039;&#039;&#039; (object): The params for the WS call.&lt;br /&gt;
* &#039;&#039;&#039;preSets&#039;&#039;&#039; (object): Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &#039;&#039;&#039;useOtherDataForWS&#039;&#039;&#039; (any): Whether to include &#039;&#039;otherdata&#039;&#039; (from the &#039;&#039;get_content&#039;&#039; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&#039;&#039;&#039;null, false or empty string&#039;&#039;&#039;) all the &#039;&#039;otherdata&#039;&#039; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that [useOtherDataForWS]=&amp;quot;&amp;quot; is the same as not supplying it, so nothing will be copied.&lt;br /&gt;
* &#039;&#039;&#039;form&#039;&#039;&#039; (string): ID or name to identify a form in the template. The form will be obtained from &#039;&#039;document.forms&#039;&#039;. If supplied and form is found, the form data will be retrieved and sent to the WS. If your form contains an ion-radio, ion-checkbox or ion-select, please see [[Mobile_support_for_plugins#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of ion-radio, ion-checkbox or ion-select aren&#039;t sent to my WS]].&lt;br /&gt;
* &#039;&#039;&#039;confirmMessage&#039;&#039;&#039; (string): Message to confirm the action when the user clicks the element. If not supplied, no confirmation. If supplied but empty, default message (&amp;quot;Are you sure?&amp;quot;).&lt;br /&gt;
* &#039;&#039;&#039;showError&#039;&#039;&#039; (boolean): Whether to show an error message if the WS call fails. Defaults to true. This field was added in v3.5.2.&lt;br /&gt;
* &#039;&#039;&#039;successMessage&#039;&#039;&#039; (string): Message to show on success. If not supplied, no message. If supplied but empty, default message (“Success”).&lt;br /&gt;
* &#039;&#039;&#039;goBackOnSuccess&#039;&#039;&#039; (boolean): Whether to go back if the WS call is successful.&lt;br /&gt;
* &#039;&#039;&#039;refreshOnSuccess&#039;&#039;&#039; (boolean): Whether to refresh the current view if the WS call is successful.&lt;br /&gt;
* &#039;&#039;&#039;onSuccess&#039;&#039;&#039; (Function): A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in v3.5.2.&lt;br /&gt;
* &#039;&#039;&#039;onError&#039;&#039;&#039; (Function): A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in v3.5.2.&lt;br /&gt;
* &#039;&#039;&#039;onDone&#039;&#039;&#039; (Function): A function to call when the WS call finishes (either success or fail). This field was added in v3.5.2.&lt;br /&gt;
&lt;br /&gt;
Example usages:&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;code php&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button core-site-plugins-call-ws name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage successMessage refreshOnSuccess=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;
    {{ &#039;plugin.mod_certificate.senddata&#039; | translate }}&lt;br /&gt;
&amp;lt;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A button to send some data to the server using cache without confirming, going back on success and using userid from otherdata:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button core-site-plugins-call-ws name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; 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;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Same example as the previous one but implementing a custom JS code to run on success:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button core-site-plugins-call-ws name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [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;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code javascript&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====core-site-plugins-call-ws-new-content====&lt;br /&gt;
&lt;br /&gt;
Directive to call a WS when the element is clicked and load a new content passing the WS result as args. 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.&lt;br /&gt;
&lt;br /&gt;
Data that can be passed to the directive:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;name&#039;&#039;&#039; (string): The name of the WS to call.&lt;br /&gt;
* &#039;&#039;&#039;params&#039;&#039;&#039; (object): The params for the WS call.&lt;br /&gt;
* &#039;&#039;&#039;preSets&#039;&#039;&#039; (object): Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &#039;&#039;&#039;useOtherDataForWS&#039;&#039;&#039; (any): Whether to include &#039;&#039;otherdata&#039;&#039; (from the &#039;&#039;get_content&#039;&#039; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&#039;&#039;&#039;null, false or empty string&#039;&#039;&#039;) all the &#039;&#039;otherdata&#039;&#039; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that [useOtherDataForWS]=&amp;quot;&amp;quot; is the same as not supplying it, so nothing will be copied.&lt;br /&gt;
* &#039;&#039;&#039;form&#039;&#039;&#039; (string): ID or name to identify a form in the template. The form will be obtained from &#039;&#039;document.forms&#039;&#039;. If supplied and form is found, the form data will be retrieved and sent to the WS. If your form contains an ion-radio, ion-checkbox or ion-select, please see [[Mobile_support_for_plugins#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of ion-radio, ion-checkbox or ion-select aren&#039;t sent to my WS]].&lt;br /&gt;
* &#039;&#039;&#039;confirmMessage&#039;&#039;&#039; (string): Message to confirm the action when the user clicks the element. If not supplied, no confirmation. If supplied but empty, default message (&amp;quot;Are you sure?&amp;quot;).&lt;br /&gt;
* &#039;&#039;&#039;showError&#039;&#039;&#039; (boolean): Whether to show an error message if the WS call fails. Defaults to true. This field was added in v3.5.2.&lt;br /&gt;
* &#039;&#039;&#039;component&#039;&#039;&#039; (string): The component of the new content.&lt;br /&gt;
* &#039;&#039;&#039;method&#039;&#039;&#039; (string): The method to get the new content.&lt;br /&gt;
* &#039;&#039;&#039;args&#039;&#039;&#039; (object): The params to get the new content.&lt;br /&gt;
* &#039;&#039;&#039;title&#039;&#039;&#039; (string): The title to display with the new content. Only if samePage=false.&lt;br /&gt;
* &#039;&#039;&#039;samePage&#039;&#039;&#039; (boolean): Whether to display the content in same page or open a new one. Defaults to new page.&lt;br /&gt;
* &#039;&#039;&#039;useOtherData&#039;&#039;&#039; (any): Whether to include &#039;&#039;otherdata&#039;&#039; (from the &#039;&#039;get_content&#039;&#039; WS call) in the args for the new &#039;&#039;get_content&#039;&#039; call. The format is the same as in &#039;&#039;useOtherDataForWS&#039;&#039;.&lt;br /&gt;
* &#039;&#039;&#039;jsData&#039;&#039;&#039; (any): JS variables to pass to the new page so they can be used in the template or JS. If true is supplied instead of an object, all initial variables from current page will be copied. This field was added in v3.5.2.&lt;br /&gt;
* &#039;&#039;&#039;newContentPreSets&#039;&#039;&#039; (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;
* &#039;&#039;&#039;onSuccess&#039;&#039;&#039; (Function): A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in v3.5.2.&lt;br /&gt;
* &#039;&#039;&#039;onError&#039;&#039;&#039; (Function): A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in v3.5.2.&lt;br /&gt;
* &#039;&#039;&#039;onDone&#039;&#039;&#039; (Function): A function to call when the WS call finishes (either success or fail). This field was added in v3.5.2.&lt;br /&gt;
&lt;br /&gt;
Example usages:&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;code php&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button core-site-plugins-call-ws-new-content name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; confirmMessage title=&amp;quot;&amp;lt;% certificate.name %&amp;gt;&amp;quot; component=&amp;quot;mod_certificate&amp;quot; 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;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&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 &#039;&#039;userid&#039;&#039; from &#039;&#039;otherdata&#039;&#039;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button core-site-plugins-call-ws-new-content name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; component=&amp;quot;mod_certificate&amp;quot; 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; 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;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Same example as the previous one but implementing a custom JS code to run on success:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button core-site-plugins-call-ws-new-content name=&amp;quot;mod_certificate_get_issued_certificates&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; component=&amp;quot;mod_certificate&amp;quot; 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; 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;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code javascript&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====core-site-plugins-call-ws-on-load====&lt;br /&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.&lt;br /&gt;
&lt;br /&gt;
Note that this will cause an error to appear on each page load if the user is offline in v3.5.1 and older, the bug was fixed in v3.5.2.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;name&#039;&#039;&#039; (string): The name of the WS to call.&lt;br /&gt;
* &#039;&#039;&#039;params&#039;&#039;&#039; (object): The params for the WS call.&lt;br /&gt;
* &#039;&#039;&#039;preSets&#039;&#039;&#039; (object): Extra options for the WS call: whether to use cache or not, etc.&lt;br /&gt;
* &#039;&#039;&#039;useOtherDataForWS&#039;&#039;&#039; (any): Whether to include &#039;&#039;otherdata&#039;&#039; (from the &#039;&#039;get_content&#039;&#039; WS call) in the params for the WS call. If not supplied, no other data will be added. If supplied but empty (&#039;&#039;&#039;null, false or empty string&#039;&#039;&#039;) all the &#039;&#039;otherdata&#039;&#039; will be added. If it’s an array, it will only copy the properties whose names are in the array. Please notice that [useOtherDataForWS]=&amp;quot;&amp;quot; is the same as not supplying it, so nothing will be copied.&lt;br /&gt;
* &#039;&#039;&#039;form&#039;&#039;&#039; (string): ID or name to identify a form in the template. The form will be obtained from &#039;&#039;document.forms&#039;&#039;. If supplied and form is found, the form data will be retrieved and sent to the WS. If your form contains an ion-radio, ion-checkbox or ion-select, please see [[Mobile_support_for_plugins#Values_of_ion-radio.2C_ion-checkbox_or_ion-select_aren.27t_sent_to_my_WS|Values of ion-radio, ion-checkbox or ion-select aren&#039;t sent to my WS]].&lt;br /&gt;
* &#039;&#039;&#039;onSuccess&#039;&#039;&#039; (Function): A function to call when the WS call is successful (HTTP call successful and no exception returned). This field was added in v3.5.2.&lt;br /&gt;
* &#039;&#039;&#039;onError&#039;&#039;&#039; (Function): A function to call when the WS call fails (HTTP call fails or an exception is returned). This field was added in v3.5.2.&lt;br /&gt;
* &#039;&#039;&#039;onDone&#039;&#039;&#039; (Function): A function to call when the WS call finishes (either success or fail). This field was added in v3.5.2.&lt;br /&gt;
&lt;br /&gt;
Example usage:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;span core-site-plugins-call-ws-on-load name=&amp;quot;mod_certificate_view_certificate&amp;quot; [params]=&amp;quot;{certificateid: &amp;lt;% certificate.id %&amp;gt;}&amp;quot; [preSets]=&amp;quot;{getFromCache: 0, saveToCache: 0}&amp;quot; (onSuccess)=&amp;quot;callDone($event)&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code javascript&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Advanced features==&lt;br /&gt;
&lt;br /&gt;
===Display the plugin only if certain conditions are met===&lt;br /&gt;
&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 &amp;quot;init&amp;quot; method (for more info, please see the [[Mobile_support_for_plugins#Initialization|Initialization]] section ahead).&lt;br /&gt;
&lt;br /&gt;
All the init 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 throw an exception in this init method. It&#039;s recommended to include a message explaining why the plugin isn&#039;t available for the current user, this exception will be logged in the Javascript console.&lt;br /&gt;
&lt;br /&gt;
On the other hand, you might want to display a plugin only for certain courses (&#039;&#039;CoreCourseOptionsDelegate&#039;&#039;) or only if the user is viewing certain users&#039; profiles (&#039;&#039;CoreUserDelegate&#039;&#039;). This can be achieved with the init method too.&lt;br /&gt;
&lt;br /&gt;
In the init method you can return a &amp;quot;restrict&amp;quot; property with two fields in it: &#039;&#039;courses&#039;&#039; and &#039;&#039;users&#039;&#039;. If you return a list of courses IDs in this restrict 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;
&lt;br /&gt;
===Using “otherdata”===&lt;br /&gt;
&lt;br /&gt;
The values returned by the functions in otherdata are added to a variable so they can be used both in Javascript and in templates. The otherdata returned by a init call is added to a variable named INIT_OTHERDATA, while the otherdata returned by a &#039;&#039;get_content&#039;&#039; WS call is added to a variable named CONTENT_OTHERDATA.&lt;br /&gt;
&lt;br /&gt;
The otherdata returned by a init call will be passed to the JS and template of all the get_content calls in that handler. The otherdata returned by a get_content call will only be passed to the JS and template returned by that get_content call.&lt;br /&gt;
&lt;br /&gt;
This means that, in your Javascript, you can access and use these data like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
this.CONTENT_OTHERDATA.myVar&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
And in the template you could use it like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
{{ CONTENT_OTHERDATA.myVar }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;myVar&#039;&#039; is the name we put to one of our variables, it can be the name you want. In the example above, this is the otherdata returned by the PHP method:&lt;br /&gt;
&lt;br /&gt;
array(&#039;myVar&#039; =&amp;gt; &#039;Initial value&#039;)&lt;br /&gt;
&lt;br /&gt;
====Example====&lt;br /&gt;
&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 WebService. This can be done using otherdata.&lt;br /&gt;
&lt;br /&gt;
We will return the initial value of the input in the otherdata of our PHP method:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; array(&#039;myVar&#039; =&amp;gt; &#039;My initial value&#039;),&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then in the template we will use it like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;ion-item text-wrap&amp;gt;&lt;br /&gt;
    &amp;lt;ion-label stacked&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;button ion-button block 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;/button&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the example above, we are creating an input text and we use &#039;&#039;[(ngModel)]&#039;&#039; to use the value in &#039;&#039;myVar&#039;&#039; as the initial value and to store the changes in the same &#039;&#039;myVar&#039;&#039; variable. This means that the initial value of the input will be “My initial value”, and if the user changes the value of the input these changes will be applied to the &#039;&#039;myVar&#039;&#039; 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 directive core-site-plugins-call-ws. We use the &#039;&#039;useOtherDataForWS&#039;&#039; attribute to specify which variable from &#039;&#039;otherdata&#039;&#039; we want to send to our WebService. So if the user enters “A new value” in the input and then clicks the button, it will call the WebService &#039;&#039;mod_certificate_my_webservice&#039;&#039; and will send as a param: myVar -&amp;gt; “A new value”.&lt;br /&gt;
&lt;br /&gt;
We can achieve the same result using the &#039;&#039;params&#039;&#039; attribute of the core-site-plugins-call-ws directive instead of using &#039;&#039;useOtherDataForWS&#039;&#039;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button block color=&amp;quot;light&amp;quot; core-site-plugins-call-ws 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;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The WebService call will be exactly the same with both buttons.&lt;br /&gt;
&lt;br /&gt;
Please notice that this example could be done without using otherdata too, using the “&#039;&#039;form&#039;&#039;” input of the &#039;&#039;core-site-plugins-call-ws directive&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
===Running JS code after a content template has loaded===&lt;br /&gt;
&lt;br /&gt;
When you return JavaScript code from a handler function using the &#039;javascript&#039; 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 setTimeout to call it. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
return [&lt;br /&gt;
    &#039;template&#039; =&amp;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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Note: 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 init template, so that it does not get loaded again with each page of content.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
===JS functions visible in the templates===&lt;br /&gt;
&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;
&lt;br /&gt;
* &#039;&#039;&#039;openContent(title: string, args: any, component?: string, method?: string)&#039;&#039;&#039;: Open a new page to display some new content. You need to specify the &#039;&#039;title&#039;&#039; of the new page and the &#039;&#039;args&#039;&#039; to send to the method. If &#039;&#039;component&#039;&#039; and &#039;&#039;method&#039;&#039; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
* &#039;&#039;&#039;refreshContent(showSpinner = true)&#039;&#039;&#039;: 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 false as a parameter.&lt;br /&gt;
* &#039;&#039;&#039;updateContent(args: any, component?: string, method?: string)&#039;&#039;&#039;: Refresh the current content using different params. You need to specify the &#039;&#039;args&#039;&#039; to send to the method. If &#039;&#039;component&#039;&#039; and &#039;&#039;method&#039;&#039; aren&#039;t provided, it will use the same as in the current page.&lt;br /&gt;
&lt;br /&gt;
====Examples====&lt;br /&gt;
&lt;br /&gt;
=====Group selector=====&lt;br /&gt;
&lt;br /&gt;
Imagine we have an activity that uses groups and we want to let the user select which group he wants 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 &#039;&#039;ngModel&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
======Using plain HTML======&lt;br /&gt;
&lt;br /&gt;
We need to add a &amp;quot;&#039;&#039;selected&#039;&#039;&amp;quot; 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;
&lt;br /&gt;
&amp;lt;code php&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;
        // 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 = array(&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 array(&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; array(&lt;br /&gt;
                array(&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;/code&amp;gt;&lt;br /&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;quot;selected&amp;quot; bool 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;
&lt;br /&gt;
&amp;lt;code php&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;ionChange&#039;&#039; function will be called everytime the user selects a different group with the drop down. We&#039;re using the function &#039;&#039;updateContent&#039;&#039; to update the current view using the new group. &#039;&#039;$event&#039;&#039; 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;
&lt;br /&gt;
======Using ngModel======&lt;br /&gt;
&lt;br /&gt;
ngModel is an Angular directive that allows storing the value of a certain input/select in a Javascript variable, and also the opposite way: tell the input/select which value to set. The main problem is that we cannot initialize a Javascript variable from the template (Angular doesn&#039;t have &#039;&#039;ng-init&#039;&#039; like in AngularJS), so we&#039;ll use &amp;quot;otherdata&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
In the PHP function we&#039;ll return the group that needs to be selected in the &#039;&#039;otherdata&#039;&#039; array:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&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 array(&lt;br /&gt;
            &#039;templates&#039; =&amp;gt; array(&lt;br /&gt;
                array(&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; array(&lt;br /&gt;
                &#039;group&#039; =&amp;gt; $groupid&lt;br /&gt;
            ),&lt;br /&gt;
        );&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&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 groupid in the &amp;quot;otherdata&amp;quot; array. As it&#039;s explained in the [[Mobile_support_for_plugins#Using_.E2.80.9Cotherdata.E2.80.9D|Using &amp;quot;otherdata&amp;quot;]] section, this &amp;quot;otherdata&amp;quot; is visible in the templates inside a variable named &#039;&#039;CONTENT_OTHERDATA&#039;&#039;. So in the template we&#039;ll use this variable like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;ion-select [(ngModel)]=&amp;quot;CONTENT_OTHERDATA.group&amp;quot; (ionChange)=&amp;quot;updateContent({cmid: &amp;lt;% cmid %&amp;gt;, courseid: &amp;lt;% courseid %&amp;gt;, group: CONTENT_OTHERDATA.group})&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;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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Use the rich text editor===&lt;br /&gt;
&lt;br /&gt;
The rich text editor included in the app requires a FormControl to work. You can use the library FormBuilder to create this control (or to create a whole FormGroup if you prefer).&lt;br /&gt;
&lt;br /&gt;
With the following Javascript you&#039;ll be able to create a FormControl:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
this.control = this.FormBuilder.control(this.CONTENT_OTHERDATA.rte);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the example above we&#039;re using a value returned in OTHERDATA 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;
&lt;br /&gt;
&amp;lt;code&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;&amp;lt;/core-rich-text-editor&amp;gt;&lt;br /&gt;
&amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally, there are several ways to send the value in the rich text editor to a WebService to save it. This is one of the simplest options:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button block 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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, we&#039;re passing the value of the rich text editor as a parameter to our WebService.&lt;br /&gt;
&lt;br /&gt;
===Initialization===&lt;br /&gt;
&lt;br /&gt;
All handlers can specify a “&#039;&#039;init&#039;&#039;” method in the mobile.php 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 &#039;&#039;tool_mobile_get_content&#039;&#039; WebService with the init method. This WS call will only receive the default args.&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 mobile.php data.&lt;br /&gt;
&lt;br /&gt;
The templates returned by this init method will be added to a INIT_TEMPLATES variable that will be passed to all the Javascript code of that handler. This means that the Javascript returned by the init method or the “main” method can access any of the templates HTML like this:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
this.INIT_TEMPLATES[‘main’];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
In this case, “main” is the ID of the template we want to use.&lt;br /&gt;
&lt;br /&gt;
The same happens with the &#039;&#039;otherdata&#039;&#039; returned by this init method, it is added to a INIT_OTHERDATA variable.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;restrict&#039;&#039; field returned by this init call will be used to determine if your handler is enabled or not. For example, if your handler is for the delegate &#039;&#039;CoreCourseOptionsDelegate&#039;&#039; and you return a list of courseids in restrict-&amp;gt;courses, 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 init 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 init Javascript code does something like this:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
var result = {&lt;br /&gt;
    MyAddonClass: new MyAddonClass()&lt;br /&gt;
};&lt;br /&gt;
result:&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then, for the rest of Javascript code of your handler (e.g. for the “main” method) you can use this variable like this:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
this.MyAddonClass&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Examples====&lt;br /&gt;
&lt;br /&gt;
=====Module link handler=====&lt;br /&gt;
&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 module when a link to the module is clicked. In this example we’ll create a link handler to detect links to a certificate module using a init JavaScript:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
function AddonModCertificateModuleLinkHandler() {&lt;br /&gt;
    that.CoreContentLinksModuleIndexHandler.call(this, that.CoreCourseHelperProvider, &#039;mmaModCertificate&#039;, &#039;certificate&#039;);&lt;br /&gt;
&lt;br /&gt;
    this.name = &amp;quot;AddonModCertificateLinkHandler&amp;quot;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
AddonModCertificateModuleLinkHandler.prototype = Object.create(this.CoreContentLinksModuleIndexHandler.prototype);&lt;br /&gt;
AddonModCertificateModuleLinkHandler.prototype.constructor = AddonModCertificateModuleLinkHandler;&lt;br /&gt;
&lt;br /&gt;
this.CoreContentLinksDelegate.registerHandler(new AddonModCertificateModuleLinkHandler());&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=====Advanced link handler=====&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;code javascript&amp;gt;&lt;br /&gt;
function AddonModFooLinkHandler() {&lt;br /&gt;
    ....&lt;br /&gt;
    this.pattern = RegExp(&#039;\/mod\/foo\/specialpage.php&#039;);&lt;br /&gt;
    ....&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&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;
All default handlers have a priority of 0, so 1 or higher will override the default.&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
function AddonModFooLinkHandler() {&lt;br /&gt;
    ....&lt;br /&gt;
    this.priority = 1;&lt;br /&gt;
    ....&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
======Multiple actions======&lt;br /&gt;
Once a link has been matched, the handler&#039;s getActions() method determines what the link should do.  This method has access to the URL and its parameters.&lt;br /&gt;
Different actions can be returned depending on different conditions.&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
AddonModFooLinkHandler.prototype.getActions = function(siteIds, url, params) {&lt;br /&gt;
    return [{&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;
&amp;lt;/code&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 &amp;quot;valid&amp;quot; action will be used to open the link.&lt;br /&gt;
If your handler is matched with a link, but a condition assessed in the getActions() function means you want to revert to the next highest priorty handler, you can &amp;quot;invalidate&amp;quot;&lt;br /&gt;
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 /mod/foo/, and force those with an id parameter that&#039;s not in the &amp;quot;supportedModFoos&amp;quot; array to open in the user&#039;s browser, rather than the app.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
var supportedModFoos = [...];&lt;br /&gt;
function AddonModFooLinkHandler() {&lt;br /&gt;
    this.pattern = new RegExp(&#039;\/mod\/foo\/&#039;);&lt;br /&gt;
    this.name = &amp;quot;AddonModFooLinkHandler&amp;quot;;&lt;br /&gt;
    this.priority = 1;&lt;br /&gt;
}&lt;br /&gt;
AddonModFooLinkHandler.prototype = Object.create(that.CoreContentLinksHandlerBase.prototype);&lt;br /&gt;
AddonModFooLinkHandler.prototype.constructor = AddonModFooLinkHandler;&lt;br /&gt;
AddonModFooLinkHandler.prototype.getActions = function(siteIds, url, params) {     &lt;br /&gt;
    var action = {&lt;br /&gt;
        action: function() {&lt;br /&gt;
            that.CoreUtilsProvider.openInBrowser(url);&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;
    return [action];&lt;br /&gt;
};&lt;br /&gt;
that.CoreContentLinksDelegate.registerHandler(new AddonModFooLinkHandler());&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=====Module prefetch handler=====&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;CoreCourseModuleDelegate&#039;&#039; handler allows you to define a list of &#039;&#039;offlinefunctions&#039;&#039; 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 &#039;&#039;offlinefunctions&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Here’s an example on how to create a prefetch handler using init JS:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
// Create a class that &amp;quot;inherits&amp;quot; from CoreCourseActivityPrefetchHandlerBase.&lt;br /&gt;
function AddonModCertificateModulePrefetchHandler() {&lt;br /&gt;
    that.CoreCourseActivityPrefetchHandlerBase.call(this, that.TranslateService, that.CoreAppProvider, that.CoreUtilsProvider,&lt;br /&gt;
            that.CoreCourseProvider, that.CoreFilepoolProvider, that.CoreSitesProvider, that.CoreDomUtilsProvider);&lt;br /&gt;
&lt;br /&gt;
    this.name = &amp;quot;AddonModCertificateModulePrefetchHandler&amp;quot;;&lt;br /&gt;
    this.modName = &amp;quot;certificate&amp;quot;;&lt;br /&gt;
    this.component = &amp;quot;mod_certificate&amp;quot;; // This must match the plugin identifier from db/mobile.php, otherwise the download link in the context menu will not update correctly.&lt;br /&gt;
    this.updatesNames = /^configuration$|^.*files$/;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
AddonModCertificateModulePrefetchHandler.prototype = Object.create(this.CoreCourseActivityPrefetchHandlerBase.prototype);&lt;br /&gt;
AddonModCertificateModulePrefetchHandler.prototype.constructor = AddonModCertificateModulePrefetchHandler;&lt;br /&gt;
&lt;br /&gt;
// Override the prefetch call.&lt;br /&gt;
AddonModCertificateModulePrefetchHandler.prototype.prefetch = function(module, courseId, single, dirPath) {&lt;br /&gt;
    return this.prefetchPackage(module, courseId, single, prefetchCertificate);&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;/code&amp;gt;&lt;br /&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 cmid. 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 &#039;section&#039; parameter for the mobile function &#039;mobile_document_view&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=====Single activity course format=====&lt;br /&gt;
&lt;br /&gt;
In the following example, the value of INIT_TEMPLATES[&amp;quot;main&amp;quot;] is:&lt;br /&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;
&lt;br /&gt;
This template is returned by the init method. And this is the JavaScript code returned by the init method:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
var that = this;&lt;br /&gt;
&lt;br /&gt;
function getAddonSingleActivityFormatComponent() {&lt;br /&gt;
    function AddonSingleActivityFormatComponent() {&lt;br /&gt;
        this.data = {};&lt;br /&gt;
    };&lt;br /&gt;
    AddonSingleActivityFormatComponent.prototype.constructor = AddonSingleActivityFormatComponent;&lt;br /&gt;
    AddonSingleActivityFormatComponent.prototype.ngOnChanges = function(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;
    AddonSingleActivityFormatComponent.prototype.doRefresh = function(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;
return AddonSingleActivityFormatComponent;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
function AddonSingleActivityFormatHandler() {&lt;br /&gt;
    this.name = &amp;quot;singleactivity&amp;quot;;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
AddonSingleActivityFormatHandler.prototype.constructor = AddonSingleActivityFormatHandler;&lt;br /&gt;
&lt;br /&gt;
AddonSingleActivityFormatHandler.prototype.isEnabled = function() {&lt;br /&gt;
    return true;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
AddonSingleActivityFormatHandler.prototype.canViewAllSections = function(course) {&lt;br /&gt;
    return false;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
AddonSingleActivityFormatHandler.prototype.getCourseTitle = function(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 || &amp;quot;&amp;quot;;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
AddonSingleActivityFormatHandler.prototype.displayEnableDownload = function(course) {&lt;br /&gt;
    return false;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
AddonSingleActivityFormatHandler.prototype.displaySectionSelector = function(course) {&lt;br /&gt;
    return false;&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
AddonSingleActivityFormatHandler.prototype.getCourseFormatComponent = function(injector, course) {&lt;br /&gt;
    that.Injector = injector || that.Injector;&lt;br /&gt;
&lt;br /&gt;
    return that.CoreCompileProvider.instantiateDynamicComponent(that.INIT_TEMPLATES[&amp;quot;main&amp;quot;], getAddonSingleActivityFormatComponent(), injector);&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
this.CoreCourseFormatDelegate.registerHandler(new AddonSingleActivityFormatHandler());&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Using the JavaScript API===&lt;br /&gt;
&lt;br /&gt;
The Javascript API is partly supported right now, only the delegates specified in the section [[Mobile_support_for_plugins#Templates_downloaded_on_login_and_rendered_using_JS_data_2|Templates downloaded on login and rendered using JS data]] supports it now. This API allows you to override any of the functions of the default handler. &lt;br /&gt;
&lt;br /&gt;
The “method” specified in a handler registered in the &#039;&#039;CoreUserProfileFieldDelegate&#039;&#039; will be called immediately after the init method, and the Javascript returned by this method will be run. If this Javascript code returns an object with certain functions, these function 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;
&lt;br /&gt;
&amp;lt;code javascript&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;
result;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The the &#039;&#039;getData&#039;&#039; function of the default handler will be overridden by the returned getData function.&lt;br /&gt;
&lt;br /&gt;
The default handler for &#039;&#039;CoreUserProfileFieldDelegate&#039;&#039; only has 2 functions: &#039;&#039;getComponent&#039;&#039; and &#039;&#039;getData&#039;&#039;. In addition, the JavaScript code can return an extra function named &#039;&#039;componentInit&#039;&#039; that will be executed when the component returned by &#039;&#039;getComponent&#039;&#039; is initialized.&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;
&lt;br /&gt;
&amp;lt;code javascript&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 = &amp;quot;profile_field_&amp;quot; + this.field.shortname;&lt;br /&gt;
&lt;br /&gt;
            if (this.field.param2) {&lt;br /&gt;
                this.field.maxlength = parseInt(this.field.param2, 10) || &amp;quot;&amp;quot;;&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            this.field.inputType = that.CoreUtilsProvider.isTrueOrOne(this.field.param3) ? &amp;quot;password&amp;quot; : &amp;quot;text&amp;quot;;&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, 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 = &amp;quot;profile_field_&amp;quot; + 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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Translate dynamic strings===&lt;br /&gt;
&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;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
&amp;lt;ion-card&amp;gt;&lt;br /&gt;
    &amp;lt;ion-card-content translate&amp;gt;&lt;br /&gt;
        plugin.mod_myactivity.&amp;lt;% status %&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This could save you from having to write something like when only one value should be displayed:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Using strings with dates===&lt;br /&gt;
&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;
&lt;br /&gt;
&amp;lt;code php&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can localise the string correctly in your template using something like the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A Unix timestamp must be multiplied by 1000 as the Mobile App expects millisecond timestamps, where as Unix timestamps are in seconds.&lt;br /&gt;
&lt;br /&gt;
===Support push notification clicks===&lt;br /&gt;
&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 &#039;&#039;contexturl&#039;&#039; in your notification. When the notification is clicked, the app will try to open the &#039;&#039;contexturl&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &#039;&#039;contexturl&#039;&#039; 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 &#039;&#039;customdata&#039;&#039; array that contains an &#039;&#039;appurl&#039;&#039; property:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&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;/code&amp;gt;&lt;br /&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 [[Mobile_support_for_plugins#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 [[Mobile_support_for_plugins#Advanced_link_handler|creating an advanced link handler]], but you&#039;ll have to use &#039;&#039;CorePushNotificationsDelegate&#039;&#039; and your handler will have to implement the properties and functions defined in the interface [https://github.com/moodlehq/moodlemobile2/blob/master/src/core/pushnotifications/providers/delegate.ts#L24 CorePushNotificationsClickHandler].&lt;br /&gt;
&lt;br /&gt;
==Troubleshooting==&lt;br /&gt;
&lt;br /&gt;
=== Invalid response received ===&lt;br /&gt;
&lt;br /&gt;
You might receive this error when using the &amp;quot;core-site-plugins-call-ws&amp;quot; directive or similar. By default, the app expects all WebService calls to return an object, if your WebService returns another type (string, bool, ...) then you need to specify it using the preSets attribute of the directive. For example, if your WS returns a boolean value, then you should specify it like this:&lt;br /&gt;
&lt;br /&gt;
[preSets]=&amp;quot;{typeExpected: &#039;boolean&#039;}&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In a similar way, if your WebService returns null you need to tell the app not to expect any result using the preSets:&lt;br /&gt;
&lt;br /&gt;
[preSets]=&amp;quot;{responseExpected: false}&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== Values of ion-radio, ion-checkbox or ion-select aren&#039;t sent to my WS ===&lt;br /&gt;
&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, ion-radio, ion-checkbox and ion-select 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 WebService.&lt;br /&gt;
&lt;br /&gt;
There are 2 workarounds to fix this problem. It seems that the next major release of Ionic framework does use HTML inputs, so these are temporary solutions.&lt;br /&gt;
&lt;br /&gt;
==== Sending the data manually ====&lt;br /&gt;
&lt;br /&gt;
The first solution is to send the missing params manually using the &amp;quot;&#039;&#039;params&#039;&#039;&amp;quot; property. We will use &#039;&#039;ngModel&#039;&#039; to store the input value in a variable, and this variable will be passed to the params. Please notice that &#039;&#039;ngModel&#039;&#039; &#039;&#039;&#039;requires&#039;&#039;&#039; the element to have a name, so if you add &#039;&#039;ngModel&#039;&#039; 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;
&lt;br /&gt;
&amp;lt;code javascript&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;button ion-button block 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;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&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;button ion-button block 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;/button&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Basically, you need to add &#039;&#039;ngModel&#039;&#039; to the affected element (in this case, the &#039;&#039;radio-group&#039;&#039;). You can put whatever name you want as the value, we used &amp;quot;responses&amp;quot;. With this, everytime the user selects a radio button the value will be stored in a variable named &amp;quot;responses&amp;quot;. Then, in the button we are passing this variable to the params of the WebService.&lt;br /&gt;
&lt;br /&gt;
Please notice that the &amp;quot;form&amp;quot; attribute has priority over &amp;quot;params&amp;quot;, so if you have an input with name=&amp;quot;responses&amp;quot; it will override what you&#039;re manually passing to params.&lt;br /&gt;
&lt;br /&gt;
==== Using a hidden input ====&lt;br /&gt;
&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 &#039;&#039;ngModel&#039;&#039; to synchronize your ion-radio/ion-checkbox/ion-select with the new hidden input. Please notice that &#039;&#039;ngModel&#039;&#039; &#039;&#039;&#039;requires&#039;&#039;&#039; the element to have a name, so if you add &#039;&#039;ngModel&#039;&#039; 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;
&lt;br /&gt;
&amp;lt;code javascript&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you should modify it like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the example above, we&#039;re using a variable named &amp;quot;responses&amp;quot; to synchronize the data between the &#039;&#039;radio-group&#039;&#039; and the hidden input. You can use whatever name you want.&lt;br /&gt;
&lt;br /&gt;
=== I can&#039;t return an object or array in otherdata ===&lt;br /&gt;
&lt;br /&gt;
If you try to return an object or an array in any field inside &#039;&#039;otherdata&#039;&#039;, the WebService call will fail with the following error:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Scalar type expected, array or object received&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Each field in &#039;&#039;otherdata&#039;&#039; 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;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; array(&#039;data&#039; =&amp;gt; json_encode($data))&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The app will automatically parse this JSON and convert it back into an array or object.&lt;br /&gt;
&lt;br /&gt;
==Examples==&lt;br /&gt;
&lt;br /&gt;
===Accepting dynamic names in a WebService===&lt;br /&gt;
&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 WebService that we have created.&lt;br /&gt;
&lt;br /&gt;
The first issue we find is that the WebService 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 &#039;&#039;_parameters()&#039;&#039; function of our new WebService, we will add this parameter:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&#039;data&#039; =&amp;gt; new external_multiple_structure(&lt;br /&gt;
     new external_single_structure(&lt;br /&gt;
        array(&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, array()&lt;br /&gt;
)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we need to adapt our form to send the data as the WebService requires it. In our template, we have a button with the directive &#039;&#039;core-site-plugins-call-ws&#039;&#039; that will send the form data to our WebService. To make this work we will have to pass the parameters manually, without using the &amp;quot;&#039;&#039;form&#039;&#039;&amp;quot; attribute, because we need to format the data before it is sent.&lt;br /&gt;
&lt;br /&gt;
Since we will send the params manually and we want it all to be sent in the same array, we will use &#039;&#039;ngModel&#039;&#039; to store the input data into a variable that we&#039;ll call &amp;quot;data&amp;quot;, but you can use the name you want. This &amp;quot;data&amp;quot; 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;
&lt;br /&gt;
{a1: &amp;quot;My answer&amp;quot;}&lt;br /&gt;
&lt;br /&gt;
So we need to add &#039;&#039;ngModel&#039;&#039; to all the inputs whose values need to be sent to the &amp;quot;data&amp;quot; WS param. Please notice that &#039;&#039;ngModel&#039;&#039; &#039;&#039;&#039;requires&#039;&#039;&#039; the element to have a name, so if you add &#039;&#039;ngModel&#039;&#039; to a certain element you need to add a name too. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;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;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see, we&#039;re using &#039;&#039;CONTENT_OTHERDATA&#039;&#039; to store the data. We do it like this because we&#039;ll use &#039;&#039;otherdata&#039;&#039; to initialize the form, setting the values the user has already stored. If you don&#039;t need to initialize the form, then you can use the variable &amp;quot;dataObject&amp;quot;, an empty object that the Mobile app creates for you: [(ngModel)]=&amp;quot;dataObject[&#039;&amp;lt;% name %&amp;gt;&#039;]&amp;quot;&lt;br /&gt;
&lt;br /&gt;
The Mobile app has a function that allows you to convert this data object into an array like the one the WS expects: &#039;&#039;objectToArrayOfObjects&#039;&#039;. So in our button we&#039;ll use this function to format the data before it&#039;s sent:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
&amp;lt;button ion-button block 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;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As you can see in the example above, we&#039;re specifying that the keys of the &amp;quot;data&amp;quot; object need to be stored in a property named &amp;quot;name&amp;quot;, and the values need to be stored in a property named &amp;quot;value&amp;quot;. If your WebService expects different names you need to change the parameters of the function &#039;&#039;objectToArrayOfObjects&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
If you open your plugin now in the Mobile app it will display an error in the Javascript console. The reason is that the variable &amp;quot;data&amp;quot; doesn&#039;t exist inside &#039;&#039;CONTENT_OTHERDATA&#039;&#039;. As it is explained in previous sections, &#039;&#039;CONTENT_OTHERDATA&#039;&#039; holds the data that you return in &#039;&#039;otherdata&#039;&#039; for your method. We&#039;ll use &#039;&#039;otherdata&#039;&#039; to initialize 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 initialize the &amp;quot;data&amp;quot; object as an empty object. Please remember that we cannot return arrays or objects in &#039;&#039;otherdata&#039;&#039;, so we&#039;ll return a JSON string.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; array(&#039;data&#039; =&amp;gt; &#039;{}&#039;)&amp;lt;/code&amp;gt;&lt;br /&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;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$userdata = get_user_responses(); // It will held the data in a format name-&amp;gt;value. Example: array(&#039;a1&#039; =&amp;gt; &#039;My value&#039;).&lt;br /&gt;
...&lt;br /&gt;
&#039;otherdata&#039; =&amp;gt; array(&#039;data&#039; =&amp;gt; json_encode($userdata))&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&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 WebService in array format.&lt;br /&gt;
&lt;br /&gt;
==Moodle plugins with mobile support==&lt;br /&gt;
&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;
&lt;br /&gt;
See the complete list in the plugins database [https://moodle.org/plugins/browse.php?list=award&amp;amp;id=6 here] (it may contain some outdated plugins)&lt;br /&gt;
[[Category:Mobile]]&lt;/div&gt;</summary>
		<author><name>Colin.bernard</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Activity_modules&amp;diff=56284</id>
		<title>Activity modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Activity_modules&amp;diff=56284"/>
		<updated>2019-07-17T18:11:03Z</updated>

		<summary type="html">&lt;p&gt;Colin.bernard: The effects of course recycle bin on &amp;lt;modname&amp;gt;_delete_instance and why it causes it not to run immediately. See: https://moodle.org/mod/forum/discuss.php?d=362481&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Activity modules reside in the &#039;&#039;&#039;/mod&#039;&#039;&#039; directory.&lt;br /&gt;
&lt;br /&gt;
Each module is in a separate subdirectory and consists of a number of &#039;&#039;&#039;&#039;mandatory files&#039;&#039;&#039;&#039; and any other files the developer is going to use. The below image is an example of the certificate module&#039;s filestructure. Please note, any reference to &amp;lt;modname&amp;gt; in this documentation should be replaced by the name of your module.&lt;br /&gt;
&lt;br /&gt;
Below is an example of the file structure for the certificate plugin.&lt;br /&gt;
&lt;br /&gt;
[[File:Activities_file_structure_example.jpg]]&lt;br /&gt;
&lt;br /&gt;
== Standard Files and their Functions ==&lt;br /&gt;
&lt;br /&gt;
There are several files that are crucial to Moodle. These files are used to install your module and then integrate it into the Moodle system. Each file has a particular function, some of the files are not necessary and are only created when wanting to use the functionality it offers. Below are the list of most commonly used files.&lt;br /&gt;
&lt;br /&gt;
=== Backup Folder ===&lt;br /&gt;
&lt;br /&gt;
This is the location to place the files that define how your module is going to behave when a course backup or restore is performed. You may have added a lot of information to the database for a specific module that you do not wish to enter again when restoring a course, this is where you define what should be saved during a backup and how to then restore this information from the Moodle backup.&lt;br /&gt;
&lt;br /&gt;
To read more on this please visit [[Backup_2.0_for_developers]] and [[Restore_2.0_for_developers]].&lt;br /&gt;
&lt;br /&gt;
=== DB Folder ===&lt;br /&gt;
&lt;br /&gt;
==== access.php ====&lt;br /&gt;
&lt;br /&gt;
This is where you define what capabilities your plugin will create. Note, if you add new capabilities to this file after your plugin has been installed you will need to increase the version number in your version.php file (discussed later) in order for them to be installed.&lt;br /&gt;
&lt;br /&gt;
An example of the file is below -&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&lt;br /&gt;
$capabilities = array(&lt;br /&gt;
&lt;br /&gt;
    &#039;mod/certificate:addinstance&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;riskbitmask&#039; =&amp;gt; RISK_XSS,&lt;br /&gt;
        &#039;captype&#039; =&amp;gt; &#039;write&#039;,&lt;br /&gt;
        &#039;contextlevel&#039; =&amp;gt; CONTEXT_COURSE,&lt;br /&gt;
        &#039;archetypes&#039; =&amp;gt; array(&lt;br /&gt;
            &#039;editingteacher&#039; =&amp;gt; CAP_ALLOW,&lt;br /&gt;
            &#039;manager&#039; =&amp;gt; CAP_ALLOW&lt;br /&gt;
        ),&lt;br /&gt;
        &#039;clonepermissionsfrom&#039; =&amp;gt; &#039;moodle/course:manageactivities&#039;&lt;br /&gt;
    ),&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In Moodle 2.3 a new capability called &#039;addinstance&#039; was added for all core modules. Moodle will look for this capability by searching for &#039;mod/&amp;lt;modname&amp;gt;:addinstance&#039; and if it is not created then it assumed that the user does have this capability with a debug message being displayed that the capability is missing. To avoid this situation it is good to add this to your module.&lt;br /&gt;
&lt;br /&gt;
We also recommend that every activity module also provides a &#039;mod/&amp;lt;modname&amp;gt;:view&#039; capability to control visibility.  (See MDL-40854)&lt;br /&gt;
&lt;br /&gt;
For consistency and ease of use for the teachers it is advisable to use the following archetypes with the &amp;lt;code php&amp;gt;mod/&amp;lt;modname&amp;gt;:view&amp;lt;/code&amp;gt; capability:&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&#039;mod/MODULENAME:view&#039; =&amp;gt; array(&lt;br /&gt;
    &#039;captype&#039; =&amp;gt; &#039;read&#039;,&lt;br /&gt;
    &#039;contextlevel&#039; =&amp;gt; CONTEXT_MODULE,&lt;br /&gt;
    &#039;archetypes&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;guest&#039; =&amp;gt; CAP_ALLOW,&lt;br /&gt;
        &#039;student&#039; =&amp;gt; CAP_ALLOW,&lt;br /&gt;
        &#039;teacher&#039; =&amp;gt; CAP_ALLOW,&lt;br /&gt;
        &#039;editingteacher&#039; =&amp;gt; CAP_ALLOW,&lt;br /&gt;
        &#039;manager&#039; =&amp;gt; CAP_ALLOW&lt;br /&gt;
    )&lt;br /&gt;
)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For further information on what each attribute in that capabilities array means visit [[NEWMODULE_Adding_capabilities]].&lt;br /&gt;
&lt;br /&gt;
==== events.php ====&lt;br /&gt;
Setup observer(s) for monitoring events&lt;br /&gt;
&lt;br /&gt;
Once we got the observer in place, now we need to tell Moodle about it. We do this by adding an entry in admin/tool/mylog/db/events.php&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$observers = array(&lt;br /&gt;
    array(&lt;br /&gt;
        &#039;eventname&#039; =&amp;gt; &#039;*&#039;,&lt;br /&gt;
        &#039;callback&#039;  =&amp;gt; &#039;\tool_mylog\log\observer::store&#039;,&lt;br /&gt;
        &#039;internal&#039;  =&amp;gt; false, // This means that we get events only after transaction commit.&lt;br /&gt;
        &#039;priority&#039;  =&amp;gt; 1000,&lt;br /&gt;
    ),&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(More info about this file can be found on [https://docs.moodle.org/dev/Writing_log_managers#I_am_watching_you])&lt;br /&gt;
&lt;br /&gt;
==== install.xml ====&lt;br /&gt;
&lt;br /&gt;
This file is used on installation of your module; it defines the associated database tables.  For each module, the database must have a main table with the same name as the module itself.  In addition, other database tables may be defined.  In order to create the XML file please view [[XMLDB_editor]]. Please note, in the XML file the table names are listed without the config.php prefix, this is automatically used when creating the tables and does not need to be specified.&lt;br /&gt;
&lt;br /&gt;
An example of the file generated by the [[XMLDB_editor]] is below - it contains the structure of the table certificate and certificate_issues:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; ?&amp;gt;&lt;br /&gt;
&amp;lt;XMLDB PATH=&amp;quot;mod/certificate/db&amp;quot; VERSION=&amp;quot;20120925&amp;quot; COMMENT=&amp;quot;XMLDB file for Moodle mod/certificate&amp;quot;&lt;br /&gt;
    xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;&lt;br /&gt;
    xsi:noNamespaceSchemaLocation=&amp;quot;../../../lib/xmldb/xmldb.xsd&amp;quot;&lt;br /&gt;
&amp;gt;&lt;br /&gt;
  &amp;lt;TABLES&amp;gt;&lt;br /&gt;
    &amp;lt;TABLE NAME=&amp;quot;certificate&amp;quot; COMMENT=&amp;quot;Defines certificates&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;FIELDS&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;id&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; SEQUENCE=&amp;quot;true&amp;quot;/&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;course&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;name&amp;quot; TYPE=&amp;quot;char&amp;quot; LENGTH=&amp;quot;255&amp;quot; NOTNULL=&amp;quot;true&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;intro&amp;quot; TYPE=&amp;quot;text&amp;quot; LENGTH=&amp;quot;small&amp;quot; NOTNULL=&amp;quot;false&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;introformat&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;4&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;emailteachers&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;1&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;emailothers&amp;quot; TYPE=&amp;quot;text&amp;quot; LENGTH=&amp;quot;small&amp;quot; NOTNULL=&amp;quot;false&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;savecert&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;1&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;reportcert&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;1&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;delivery&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;3&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;requiredtime&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;type&amp;quot; TYPE=&amp;quot;char&amp;quot; LENGTH=&amp;quot;50&amp;quot; NOTNULL=&amp;quot;true&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;orientation&amp;quot; TYPE=&amp;quot;char&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;width&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;height&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;backgroundimage&amp;quot; TYPE=&amp;quot;char&amp;quot; LENGTH=&amp;quot;255&amp;quot; NOTNULL=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;timecreated&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;timemodified&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/FIELDS&amp;gt;&lt;br /&gt;
      &amp;lt;KEYS&amp;gt;&lt;br /&gt;
        &amp;lt;KEY NAME=&amp;quot;primary&amp;quot; TYPE=&amp;quot;primary&amp;quot; FIELDS=&amp;quot;id&amp;quot; COMMENT=&amp;quot;Primary key for certificate&amp;quot;/&amp;gt;&lt;br /&gt;
      &amp;lt;/KEYS&amp;gt;&lt;br /&gt;
    &amp;lt;/TABLE&amp;gt;&lt;br /&gt;
    &amp;lt;TABLE NAME=&amp;quot;certificate_issues&amp;quot; COMMENT=&amp;quot;Info about issued certificates&amp;quot;&amp;gt;&lt;br /&gt;
      &amp;lt;FIELDS&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;id&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; SEQUENCE=&amp;quot;true&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;userid&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;certificateid&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;timecreated&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; UNSIGNED=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/FIELDS&amp;gt;&lt;br /&gt;
      &amp;lt;KEYS&amp;gt;&lt;br /&gt;
        &amp;lt;KEY NAME=&amp;quot;primary&amp;quot; TYPE=&amp;quot;primary&amp;quot; FIELDS=&amp;quot;id&amp;quot; COMMENT=&amp;quot;Primary key for certificate_issues&amp;quot; /&amp;gt;&lt;br /&gt;
        &amp;lt;KEY NAME=&amp;quot;certificate&amp;quot; TYPE=&amp;quot;foreign&amp;quot; FIELDS=&amp;quot;certificateid&amp;quot; REFTABLE=&amp;quot;certificate&amp;quot; REFFIELDS=&amp;quot;id&amp;quot; /&amp;gt;&lt;br /&gt;
      &amp;lt;/KEYS&amp;gt;&lt;br /&gt;
    &amp;lt;/TABLE&amp;gt;&lt;br /&gt;
  &amp;lt;/TABLES&amp;gt;&lt;br /&gt;
&amp;lt;/XMLDB&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are some fields that the Moodle core expects to be present in every activity module&#039;s main table (or just became de-facto standard):&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Field name&lt;br /&gt;
! Properties&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| id&lt;br /&gt;
| INT(10),  auto sequence&lt;br /&gt;
| primary key for the table (as any other table in Moodle)&lt;br /&gt;
|-&lt;br /&gt;
| course&lt;br /&gt;
| INT(10), foreign key to the &amp;lt;tt&amp;gt;course&amp;lt;/tt&amp;gt; table&lt;br /&gt;
| id of the course this activity is part of&lt;br /&gt;
|-&lt;br /&gt;
| name&lt;br /&gt;
| CHAR(255)&lt;br /&gt;
| holds the name of the activity module instance&lt;br /&gt;
|-&lt;br /&gt;
| timemodified&lt;br /&gt;
| INT(10)&lt;br /&gt;
| timestamp of when the instance was last modified&lt;br /&gt;
|-&lt;br /&gt;
| intro&lt;br /&gt;
| TEXT&lt;br /&gt;
| standard field to hold the activity&#039;s description (see FEATURE_MOD_INTRO)&lt;br /&gt;
|-&lt;br /&gt;
| introformat&lt;br /&gt;
| INT(4)&lt;br /&gt;
| the format of the text in the intro field&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==== upgrade.php ====&lt;br /&gt;
&lt;br /&gt;
This file handles upgrading the module to match the latest version. After creating a module and using it extensively on your site (and others) you may want to extend the functionality of your module. Using the certificate example, a suggestion was made that a unique code could be generated for each certificate issued and displayed if a setting was selected. This requires two new database fields, one to store whether the creator of the certificate has chosen to display the code on the certificate and another to store the actual code for each student. This is where the upgrade.php script becomes used. The install.xml file is only executed once, that is when your module is first installed, so adding these two extra columns to this file does not change the database structure for users who have already installed the module. So, to perform this upgrade you need to do three things.&lt;br /&gt;
&lt;br /&gt;
1. Add the new columns to the install.xml file so that any users who install the module after this point are given the new table structure.&amp;lt;br /&amp;gt;&lt;br /&gt;
2. Add the instructions for the upgrade.php files.&amp;lt;br /&amp;gt;&lt;br /&gt;
3. Update the version number in your version.php file.&amp;lt;br /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In this example we are only adding two new columns to the database, so we can use the [[XMLDB_editor]] to change the install.xml file AND create the upgrade path.&lt;br /&gt;
&lt;br /&gt;
An example of the upgrade.php file is as follows -&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function xmldb_certificate_upgrade($oldversion=0) {&lt;br /&gt;
    if ($oldversion &amp;lt; 2012091800) {&lt;br /&gt;
        // Add new fields to certificate table.&lt;br /&gt;
        $table = new xmldb_table(&#039;certificate&#039;);&lt;br /&gt;
        $field = new xmldb_field(&#039;showcode&#039;);&lt;br /&gt;
        $field-&amp;gt;set_attributes(XMLDB_TYPE_INTEGER, &#039;1&#039;, XMLDB_UNSIGNED, XMLDB_NOTNULL, null, &#039;0&#039;, &#039;savecert&#039;);&lt;br /&gt;
        if (!$dbman-&amp;gt;field_exists($table, $field)) {&lt;br /&gt;
            $dbman-&amp;gt;add_field($table, $field);&lt;br /&gt;
        }&lt;br /&gt;
        // Add new fields to certificate_issues table.&lt;br /&gt;
        $table = new xmldb_table(&#039;certificate_issues&#039;);&lt;br /&gt;
        $field = new xmldb_field(&#039;code&#039;);&lt;br /&gt;
        $field-&amp;gt;set_attributes(XMLDB_TYPE_CHAR, &#039;50&#039;, null, null, null, null, &#039;certificateid&#039;);&lt;br /&gt;
        if (!$dbman-&amp;gt;field_exists($table, $field)) {&lt;br /&gt;
            $dbman-&amp;gt;add_field($table, $field);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        // Certificate savepoint reached.&lt;br /&gt;
        upgrade_mod_savepoint(true, 2012091800, &#039;certificate&#039;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Whenever you change the version in your version.php module Moodle will look to see if anything needs to be done. The version that is stored in the database is passed to the xmldb_&amp;lt;modname&amp;gt;_upgrade function as the variable $oldversion in this case. In this example lets say that the initial version was 2012091600, since this is less than 2012091800 (the new value we put in the version.php file) we will execute the code in the if statement which will then update the version stored in the database meaning this if statement is never executed again. For more extensive details on this please see [[Upgrade_API]].&lt;br /&gt;
&lt;br /&gt;
==== mobile.php ====&lt;br /&gt;
&lt;br /&gt;
New in Moodle 3.1&lt;br /&gt;
&lt;br /&gt;
This is where you define the [[Moodle Mobile Remote addons]] included in the plugin. &lt;br /&gt;
&lt;br /&gt;
The Moodle Mobile remote add-on is the mobile app version of the plugin that will be loaded when a user accesses the plugin on the app.&lt;br /&gt;
&lt;br /&gt;
You can include several Mobile add-ons. For each add-on you must indicate a unique name (it must be unique so we recommend not to use generic names, the best way would be to use the component name of the Moodle plugin the add-on will support).&lt;br /&gt;
&lt;br /&gt;
Names cannot include blank spaces, and we recommend not to use capitals.&lt;br /&gt;
&lt;br /&gt;
Each addon referenced here must be included as a zip file containing the javascript/html/css code in the plugin mobile/ folder. The name of the zip must be the same as the addon name.&lt;br /&gt;
&lt;br /&gt;
For the following example the file name path would be: mobile/mod_certificate.zip&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&lt;br /&gt;
$addons = array(&lt;br /&gt;
    &amp;quot;mod_certificate&amp;quot; =&amp;gt; array(&lt;br /&gt;
       &amp;quot;dependencies&amp;quot; =&amp;gt; array(&amp;quot;pdfbuilder&amp;quot;)&lt;br /&gt;
    ),&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The dependencies field is optional, if you have several add-ons in the same file (or in different plugins) you can indicate here the dependencies.&lt;br /&gt;
&lt;br /&gt;
For further information on developing Mobile add-ons visit [[Moodle Mobile Remote addons]].&lt;br /&gt;
&lt;br /&gt;
=== Lang Folder ===&lt;br /&gt;
&lt;br /&gt;
This is where you store any strings you are going to use in your plugin. Each language has a specific folder that needs to be created in order for it to be used with your module. In this case we are going to use the English language (since unfortunately it is the only one I know). A folder called en is created in your lang folder that contains a file called &amp;lt;modname&amp;gt;.php that lists the translations of your string. For example, your module may have a setting called &#039;User preferences&#039;, rather than hard coding this term in your form, you use a placeholder that will then retrieve the appropriate string depending on the language being used on your Moodle site. There is one mandatory placeholder for modules called &#039;pluginname&#039; that Moodle will use when listing this module as an option to add to a course and other various pages. To keep with Moodle standards you should order your strings alphabetically by the placeholder name.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Certificate&#039;;&lt;br /&gt;
$string[&#039;userpreferences&#039;] = &#039;User preferences&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you wanted to add the French translation you would create a folder called fr and add the file &amp;lt;modname&amp;gt;.php to it.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Certificat&#039;;&lt;br /&gt;
$string[&#039;userpreferences&#039;] = &#039;Préférences d\&#039;utilisateur&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now, when using the string &#039;User preferences&#039; we will use the Moodle function get_string instead, which will get the appropriate string depending on the language being used.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
get_string(&#039;userpreferences&#039;, &#039;certificate&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Pix folder ===&lt;br /&gt;
&lt;br /&gt;
Here you simply store the icon you wish Moodle to display next to the name of your module. The name of the file should be icon.gif and is displayed with the resolution of 16 * 16. Feel free to use this folder to store other pictures you may use.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Update:&#039;&#039;&#039; Since 2.4 SVG files were introduced with all core Moodle activities. Please see [[Moodle_icons_2.4]] for more details on this.&lt;br /&gt;
&lt;br /&gt;
=== lib.php ===&lt;br /&gt;
&lt;br /&gt;
The list of functions that are possible to override in the lib.php are listed here [[NEWMODULE_Documentation#lib.php]]. The essential ones are listed below&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function certificate_add_instance($certificate);&lt;br /&gt;
function certificate_update_instance($certificate);&lt;br /&gt;
function certificate_delete_instance($id);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;modname&amp;gt;_add_instance function is passed the variables from the mod_form.php file (discussed later) as an object when you first create an activity and click submit. This is where you can take that data, do what you want with it and then insert it into the database if you wish. This is only called once when the module instance is first created, so this is where you should place the logic to add the activity.&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
The &amp;lt;modname&amp;gt;_update_instance function is passed the variables from the mod_form.php file as an object whenever you update an activity and click submit. The id of the instance you are editing is passed as the attribute instance and can be used to edit any existing values in the database for that instance.&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;&lt;br /&gt;
The &amp;lt;modname&amp;gt;_delete_instance function is passed the id of your module which you can use to delete the records from any database tables associated with that id. For example, in the certificate module the id in the certificate table is passed, and then used to delete the certificate from the database, any issues of this certificate and any files associated with it on the filesystem. When a Moodle course has the recycle bin enabled, your activity will be queued for deletion and deleted when a scheduled cron task runs. For the delete_instance function to run immediately upon activity deletion, disable the course recycle bin.&lt;br /&gt;
&lt;br /&gt;
=== mod_form.php ===&lt;br /&gt;
&lt;br /&gt;
This file is used when adding/editing a module to a course. It contains the elements that will be displayed on the form responsible for creating/installing an instance of your module. The class in the file should be called mod_&amp;lt;modname&amp;gt;_mod_form.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (!defined(&#039;MOODLE_INTERNAL&#039;)) {&lt;br /&gt;
    die(&#039;Direct access to this script is forbidden.&#039;);    ///  It must be included from a Moodle page&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
require_once($CFG-&amp;gt;dirroot.&#039;/course/moodleform_mod.php&#039;);&lt;br /&gt;
require_once($CFG-&amp;gt;dirroot.&#039;/mod/certificate/lib.php&#039;);&lt;br /&gt;
&lt;br /&gt;
class mod_certificate_mod_form extends moodleform_mod {&lt;br /&gt;
&lt;br /&gt;
    function definition() {&lt;br /&gt;
        global $CFG, $DB, $OUTPUT;&lt;br /&gt;
&lt;br /&gt;
        $mform =&amp;amp; $this-&amp;gt;_form;&lt;br /&gt;
&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;name&#039;, get_string(&#039;certificatename&#039;, &#039;certificate&#039;), array(&#039;size&#039;=&amp;gt;&#039;64&#039;));&lt;br /&gt;
        $mform-&amp;gt;setType(&#039;name&#039;, PARAM_TEXT);&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;name&#039;, null, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&lt;br /&gt;
        $ynoptions = array(0 =&amp;gt; get_string(&#039;no&#039;),&lt;br /&gt;
                           1 =&amp;gt; get_string(&#039;yes&#039;));&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;select&#039;, &#039;usecode&#039;, get_string(&#039;usecode&#039;, &#039;certificate&#039;), $ynoptions);&lt;br /&gt;
        $mform-&amp;gt;setDefault(&#039;usecode&#039;, 0);&lt;br /&gt;
        $mform-&amp;gt;addHelpButton(&#039;usecode&#039;, &#039;usecode&#039;, &#039;certificate&#039;);&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;standard_coursemodule_elements();&lt;br /&gt;
&lt;br /&gt;
        $this-&amp;gt;add_action_buttons();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above example does not contain the full file, just enough to provide you with an idea. First we create a text element called &#039;name&#039; that is required, this is obviously the name of the instance. I then created another element that stores whether a user wishes to display the unique code issued to a user when they receive the certificate or not with a default value of 0 and a help button explaining what this setting does. The function standard_coursemodule_elements adds the elements common to all modules, such as the conditional fields. The add_action_buttons function adds the submit and cancel buttons to the form. This data will be passed to either &amp;lt;modname&amp;gt;_add_instance or &amp;lt;modname&amp;gt;_update_instance depending on whether you are adding a new instance or updating a current one. You can also add validation to this form, just like any other form in Moodle. For more information on how to create forms in Moodle see [[Form_API]].&lt;br /&gt;
&lt;br /&gt;
=== index.php ===&lt;br /&gt;
&lt;br /&gt;
This page is used by Moodle when listing all the instances of your module that are in a particular course with the course id being passed to this script. The beginning of the page should contain the following -&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
require_once(&#039;../../config.php&#039;);&lt;br /&gt;
&lt;br /&gt;
$id = required_param(&#039;id&#039;, PARAM_INT);           // Course ID&lt;br /&gt;
&lt;br /&gt;
// Ensure that the course specified is valid&lt;br /&gt;
if (!$course = $DB-&amp;gt;get_record(&#039;course&#039;, array(&#039;id&#039;=&amp;gt; $id))) {&lt;br /&gt;
    print_error(&#039;Course ID is incorrect&#039;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You are then free to display the list of instances as you wish.&lt;br /&gt;
&lt;br /&gt;
=== view.php ===&lt;br /&gt;
&lt;br /&gt;
When a course renders its page layout and activities it generates the links to view them using the view.php script, so the links will look like &amp;lt;wwwrootofyoursite&amp;gt;/mod/&amp;lt;modname&amp;gt;/view.php?id=4, where 4 is the course module id. For the certificate example the beginning of the view.php page looks like the following -&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
require_once(&#039;../../config.php&#039;);&lt;br /&gt;
require_once(&#039;lib.php&#039;);&lt;br /&gt;
&lt;br /&gt;
$id = required_param(&#039;id&#039;, PARAM_INT);    // Course Module ID&lt;br /&gt;
&lt;br /&gt;
if (!$cm = get_coursemodule_from_id(&#039;certificate&#039;, $id)) {&lt;br /&gt;
    print_error(&#039;Course Module ID was incorrect&#039;); // NOTE this is invalid use of print_error, must be a lang string id&lt;br /&gt;
}&lt;br /&gt;
if (!$course = $DB-&amp;gt;get_record(&#039;course&#039;, array(&#039;id&#039;=&amp;gt; $cm-&amp;gt;course))) {&lt;br /&gt;
    print_error(&#039;course is misconfigured&#039;);  // NOTE As above&lt;br /&gt;
}&lt;br /&gt;
if (!$certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, array(&#039;id&#039;=&amp;gt; $cm-&amp;gt;instance))) {&lt;br /&gt;
    print_error(&#039;course module is incorrect&#039;); // NOTE As above&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The course module id is passed to this script and set as $id, which is then used to retrieve the course data and the information from the certificate table which we can use later on as we wish.&lt;br /&gt;
&lt;br /&gt;
If you are using Moodle 2.8 and above, there is a better way to achieve most of this using a new function:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
require(&#039;../../config.php&#039;);&lt;br /&gt;
require_once(&#039;lib.php&#039;);&lt;br /&gt;
&lt;br /&gt;
$id = required_param(&#039;id&#039;, PARAM_INT);&lt;br /&gt;
list ($course, $cm) = get_course_and_cm_from_cmid($id, &#039;certificate&#039;);&lt;br /&gt;
$certificate = $DB-&amp;gt;get_record(&#039;certificate&#039;, array(&#039;id&#039;=&amp;gt; $cm-&amp;gt;instance), &#039;*&#039;, MUST_EXIST);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== version.php ===&lt;br /&gt;
&lt;br /&gt;
The version.php file keeps track of the version of your module, and other attributes, such as what version of Moodle it requires. For a full list of the attributes please see [[version.php]].&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[NEWMODULE_Documentation]]&lt;/div&gt;</summary>
		<author><name>Colin.bernard</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Templates&amp;diff=55489</id>
		<title>Templates</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Templates&amp;diff=55489"/>
		<updated>2019-01-24T05:43:32Z</updated>

		<summary type="html">&lt;p&gt;Colin.bernard: Changed variable names to match function parameters. Otherwise receive &amp;#039;variable does not exist&amp;#039; when copying code.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
= Templates =&lt;br /&gt;
&lt;br /&gt;
== What is a template? ==&lt;br /&gt;
A template is an alternative to writing blocks of HTML directly in javascript / php by concatenating strings. The end result is the same, but templates have a number of advantages:&lt;br /&gt;
* It is easier to see the final result of the template because the code for a template is very close to what the final HTML will look like&lt;br /&gt;
* Because the templating language is intentionally limited, it is hard to introduce complex logic into a template. This makes it far easier for a theme designer to override a template, without breaking the logic&lt;br /&gt;
* Templates can be rendered from javascript. This allows ajax operations to re-render a portion of the page.&lt;br /&gt;
&lt;br /&gt;
== How do I write a template? ==&lt;br /&gt;
Templates are written in a language called &amp;quot;[http://mustache.github.io/mustache.5.html Mustache]&amp;quot;. Mustache is written as HTML with additional tags used to format the display of the data. Mustache tags are made of 2 opening and closing curly braces &amp;lt;code xml&amp;gt;{{tag}}&amp;lt;/code&amp;gt;. There are a few variations of these tags that behave differently.&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{raiden}}&amp;lt;/code&amp;gt; This is a simple variable substitution. The variable named &amp;quot;raiden&amp;quot; will be searched for in the current context (and any parent contexts) and when a value is found, the entire tag will be replaced by the variable (HTML escaped).&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{{galaga}}}&amp;lt;/code&amp;gt; This is an unescaped variable substitution. Instead of escaping the variable before replacing it in the template, the variable is included raw. This is useful when the variable contains a block of HTML (for example).&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{#lemmings}} jump off cliff {{/lemmings}}&amp;lt;/code&amp;gt; These are opening and closing section tags. If the lemmings variable exists and evaluates to &amp;quot;not false&amp;quot; value, the variable is pushed on the stack, the contents of the section are parsed and included in the result. If the variable does not exist, or evaluates to false - the section will be skipped. If the variable lemmings evaluates to an array, the section will be repeated for each item in the array with the items of the array on the context. This is how to output a list.&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{^lemmings}} enjoy view {{/lemmings}}&amp;lt;/code&amp;gt; Equivalent of &amp;quot;if-not&amp;quot; block, there is no &amp;quot;else&amp;quot; in mustache.&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{&amp;gt; franken_style/name_of_template }}&amp;lt;/code&amp;gt; This is a partial. Think of it like an include. Templates can include other templates using this tag. In the called template, the data it sees (for including values is the same as the data available where the partial is included.&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{$blockvar}} ... {{/blockvar}}&amp;lt;/code&amp;gt; This is a block variable. It defines a section of the template that can be overridden when it&#039;s included in another template.&lt;br /&gt;
* &amp;lt;code xml&amp;gt;{{&amp;lt; template_name}} ... {{/template_name}}&amp;lt;/code&amp;gt; This is similar to including a partial but specifically indicates that you&#039;d like to override one or more block variables defined within the template you&#039;re including. You can override the block variables by defining a block variable within these tags that matches the name of the block variable you&#039;d like to override in the included template.&lt;br /&gt;
&lt;br /&gt;
So - putting this all together:&lt;br /&gt;
&lt;br /&gt;
recipe.mustache&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
&amp;lt;h3&amp;gt;{{recipename}}&amp;lt;/h3&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;{{description}}&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;h4&amp;gt;Ingredients&amp;lt;/h4&amp;gt;&lt;br /&gt;
&amp;lt;ol&amp;gt;&lt;br /&gt;
{{#ingredients}}&lt;br /&gt;
&amp;lt;li&amp;gt;{{.}}&amp;lt;/li&amp;gt;&lt;br /&gt;
{{/ingredients}}&lt;br /&gt;
&amp;lt;/ol&amp;gt;&lt;br /&gt;
&amp;lt;h4&amp;gt;Steps&amp;lt;/h4&amp;gt;&lt;br /&gt;
&amp;lt;ol&amp;gt;&lt;br /&gt;
{{#steps}}&lt;br /&gt;
&amp;lt;li&amp;gt;{{{.}}}&amp;lt;/li&amp;gt;&lt;br /&gt;
{{/steps}}&lt;br /&gt;
&amp;lt;/ol&amp;gt;&lt;br /&gt;
{{ &amp;gt; ratethisrecipe }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When given this data:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  recipename: &amp;quot;Cheese sandwich&amp;quot;,&lt;br /&gt;
  description: &amp;quot;Who doesn&#039;t like a good cheese sandwich?&amp;quot;,&lt;br /&gt;
  ingredients: [&amp;quot;bread&amp;quot;, &amp;quot;cheese&amp;quot;, &amp;quot;butter&amp;quot;],&lt;br /&gt;
  steps: [&amp;quot;&amp;lt;p&amp;gt;Step 1 is to spread the butter on the bread&amp;lt;/p&amp;gt;&amp;quot;, &amp;quot;&amp;lt;p&amp;gt;Step 2 is to put the cheese &amp;amp;quot;in&amp;amp;quot; the bread (not on top, or underneath)&amp;lt;/p&amp;gt;&amp;quot;]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Gives this: &amp;lt;span style=&amp;quot;font-size:4em&amp;quot;&amp;gt;😋&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
More info - there are much clearer explanations of templates on the [http://mustache.github.io/mustache.5.html Mustache] website. Try reading those pages &amp;quot;before&amp;quot; posting on stack overflow :) .&lt;br /&gt;
&lt;br /&gt;
=== Blocks (Moodle 3.0 onwards) ===&lt;br /&gt;
Blocks are a feature of Mustache that deserves a special mention. They are used as a form of inheritance - and are crucial to building a library of re-usable templates. To make use of &amp;quot;blocks&amp;quot; you define a parent template with replaceable sections. Each of those sections is marked with a &amp;quot;blocks&amp;quot; tag like this (A blocks tag looks like a regular tag, but the variable name is preceded with $):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
&amp;lt;section&amp;gt;&lt;br /&gt;
&amp;lt;h1&amp;gt;{{$sectionheading}}Default heading{{/sectionheading}}&amp;lt;/h1&amp;gt;&lt;br /&gt;
&amp;lt;div&amp;gt;&lt;br /&gt;
{{$content}}Content for section{{/content}}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/section&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now - wherever I need to re-use this template I can include it and replace the content of those sections at the same time. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{&amp;lt; section}}&lt;br /&gt;
{{$sectionheading}}Latest News{{/sectionheading}}&lt;br /&gt;
{{$content}}Nothing happened today - sorry!{{/content}}&lt;br /&gt;
{{/section}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Notice that when I include a template and I want to make use of blocks - the include tag points the other way:&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{&amp;lt; section}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Not&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{&amp;gt; section}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Blocks support looping and many other cool things - for more info see https://github.com/bobthecow/mustache.php/wiki/BLOCKS-pragma&lt;br /&gt;
&lt;br /&gt;
== Where do I put my templates? ==&lt;br /&gt;
&lt;br /&gt;
Templates go in the &amp;lt;componentdir&amp;gt;/templates folder and must have a .mustache file extension. When loading templates the template name is &amp;lt;componentname&amp;gt;/&amp;lt;filename&amp;gt; (no file extension). &lt;br /&gt;
&lt;br /&gt;
So &amp;quot;mod_lesson/timer&amp;quot; would load the template at mod/lesson/templates/timer.mustache.&lt;br /&gt;
&lt;br /&gt;
Note: Do not try and put your templates in sub folders under the &amp;quot;/templates&amp;quot; directory. This is not supported and will not work.&lt;br /&gt;
&lt;br /&gt;
== How do I call a template from javascript? ==&lt;br /&gt;
&lt;br /&gt;
Rendering a template from javascript is fairly easy. There is a new AMD module that can load/cache and render a template for you. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// This is AMD code for loading the &amp;quot;core/templates&amp;quot; module. see [Javascript Modules].&lt;br /&gt;
require([&#039;core/templates&#039;], function(templates) {&lt;br /&gt;
&lt;br /&gt;
    // This will be the context for our template. So {{name}} in the template will resolve to &amp;quot;Tweety bird&amp;quot;.&lt;br /&gt;
    var context = { name: &#039;Tweety bird&#039;, intelligence: 2 };&lt;br /&gt;
&lt;br /&gt;
    // This will call the function to load and render our template. &lt;br /&gt;
    templates.render(&#039;block_looneytunes/profile&#039;, context)&lt;br /&gt;
&lt;br /&gt;
    // It returns a promise that needs to be resoved.&lt;br /&gt;
            .then(function(html, js) {&lt;br /&gt;
                // Here eventually I have my compiled template, and any javascript that it generated.&lt;br /&gt;
                // The templates object has append, prepend and replace functions.&lt;br /&gt;
                templates.appendNodeContents(&#039;.block_looneytunes .content&#039;, html, js);&lt;br /&gt;
            }).fail(function(ex) {&lt;br /&gt;
                // Deal with this exception (I recommend core/notify exception function for this).&lt;br /&gt;
            });&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Under the hood, this did many clever things for us. It loaded the template via an ajax call if it was not cached. It found any missing lang strings in the template and loaded them in a single ajax request, it split the JS from the HTML and returned us both in easy to use way. Read on for how to nicely deal with the javascript parameter.&lt;br /&gt;
&lt;br /&gt;
Note: with some nice chaining and sugar, we can shorten the above example quite a bit:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
require([&#039;core/templates&#039;, &#039;core/notification&#039;], function(templates, notification) {&lt;br /&gt;
    var context = { name: &#039;Tweety bird&#039;, intelligence: 2 };&lt;br /&gt;
    templates.render(&#039;block_looneytunes/profile&#039;, context)&lt;br /&gt;
        .then(doneCallback)&lt;br /&gt;
        .fail(notification.exception);&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== What if a template contains javascript? ==&lt;br /&gt;
&lt;br /&gt;
Sometimes a template requires that some JS be run when it is added to the page in order to give it more features. In the template we can include blocks of javascript, but we should use a special section tag that has a &amp;quot;helper&amp;quot; method registered to handle javascript carefully. &lt;br /&gt;
&lt;br /&gt;
Example&lt;br /&gt;
profile.mustache&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;profile&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;Name: {{name}}&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;Intelligence: {{intelligence}}&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
require([&#039;jquery&#039;], function($) {&lt;br /&gt;
    // Effects! Can we have &amp;quot;blink&amp;quot;?&lt;br /&gt;
    $(&#039;#profile&#039;).slideDown();&lt;br /&gt;
});&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
If this template is rendered by PHP, the javascript is separated from the HTML, and is appended to a special section in the footer of the page &amp;quot;after&amp;quot; requirejs has loaded. This provides the optimal page loading speed. If the template is rendered by javascript, the javascript source will be passed to the &amp;quot;done&amp;quot; handler from the promise. Then, when the &amp;quot;done&amp;quot; handler has added the template to the DOM, it can call &lt;br /&gt;
&amp;lt;code javascript&amp;gt;templates.runTemplateJS(javascript);&amp;lt;/code&amp;gt; &lt;br /&gt;
which will run the javascript (by creating a new script tag and appending it to the page head).&lt;br /&gt;
&lt;br /&gt;
== What other helpers can I use? ==&lt;br /&gt;
&lt;br /&gt;
The implementation of these helpers is in classes like [https://github.com/moodle/moodle/blob/master/lib/classes/output/mustache_string_helper.php#L31 \core\output\mustache_string_helper]. This is set up in the [https://github.com/moodle/moodle/blob/master/lib/outputrenderers.php#L100 get_mustache method in renderer_base] (There should be no need to look at the implementation, unless you are interested.) If you are considering adding a new helper, it should be limited to display logic - and you need to be able to create identical helpers for the javascript and php implementations (javascript ones go in lib/amd/src/templates.js). &lt;br /&gt;
&lt;br /&gt;
=== {{# str }} ===&lt;br /&gt;
&lt;br /&gt;
There is a string helper for loading language strings.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{# str }} helloworld, mod_greeting {{/ str }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first 2 parameters are the string id and the component name. So this is effectively Mustache variant of &amp;lt;code&amp;gt;get_string(&#039;helloworld&#039;, &#039;mod_greeting&#039;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The optional third parameter defines the value for the string&#039;s &amp;lt;code&amp;gt;$a&amp;lt;/code&amp;gt; placeholder:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{# str }} iscool, mod_cool, David Beckham {{/ str }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This example would effectively do what &amp;lt;code&amp;gt;get_string(&#039;iscool&#039;, &#039;mod_cool&#039;, &#039;David Beckham&#039;)&amp;lt;/code&amp;gt; does in Moodle PHP code.&lt;br /&gt;
&lt;br /&gt;
Variable tags are allowed to define the value of the &amp;lt;code&amp;gt;$a&amp;lt;/code&amp;gt; placeholder:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{# str }} iscool, mod_cool, {{ name }} {{/ str }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For strings that accept complex placeholder, see the following section.&lt;br /&gt;
&lt;br /&gt;
=== {{# quote }} ===&lt;br /&gt;
&lt;br /&gt;
As shown in the previous section, the &amp;lt;code&amp;gt;{{# str }}&amp;lt;/code&amp;gt; helper may need complex data structures passed as the value of the &amp;lt;code&amp;gt;$a&amp;lt;/code&amp;gt; placeholder. You can use a JSON object syntax in that case:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{# str }} iscool, mod_cool, { &amp;quot;firstname&amp;quot;: &amp;quot;David&amp;quot;, &amp;quot;lastname&amp;quot;: &amp;quot;Beckham&amp;quot; } {{/ str }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you wanted to use the context values instead of literal strings, you might intuitively use something like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{! DO NOT DO THIS !}}&lt;br /&gt;
{{# str }} iscool, mod_cool, { &amp;quot;firstname&amp;quot;: &amp;quot;{{ firstname }}&amp;quot;, &amp;quot;lastname&amp;quot;: &amp;quot;{{ lastname }}&amp;quot; } {{/ str }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There is a potential problem though. If the variable tag &amp;lt;code&amp;gt;{{ firstname }}&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;{{ lastname }}&amp;lt;/code&amp;gt; evaluates to a string containing the double quote character, that will break the JSON syntax. We need to escape the double quotes potentially appearing in the variable tags. For this, use the &amp;lt;code&amp;gt;{{# quote }}&amp;lt;/code&amp;gt; helper:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{! This is OK !}}&lt;br /&gt;
{{# str }} iscool, mod_cool, { &amp;quot;firstname&amp;quot;:  {{# quote }}{{ firstname }}{{/ quote }}, &amp;quot;lastname&amp;quot;: {{# quote }}{{ lastname }}{{/ quote }} } {{/ str }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See MDL-52136 for details.&lt;br /&gt;
=== {{# pix }} ===&lt;br /&gt;
&lt;br /&gt;
There is a pix icon helper for generating pix icon tags.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{# pix }} t/edit, core, Edit David Beckham {{/ pix }}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first 2 parameters are the string id and the component name, the rest is the alt text for the image.&lt;br /&gt;
&lt;br /&gt;
=== {{# userdate }} (Moodle 3.3 onwards) ===&lt;br /&gt;
{{Moodle 3.3}}&lt;br /&gt;
This mustache template helper will format unix timestamps into a given human readable date format while using the user&#039;s timezone settings configured (if any) in Moodle. The helper will accept hardcoded values, context variables, or other helpers. &lt;br /&gt;
&lt;br /&gt;
The recommended way to use this helper is to use the string helper to get one of the core Moodle formats because they have been translated into other languages so you&#039;ll get multi-lang support for free (that&#039;s a pretty good deal!).&lt;br /&gt;
&lt;br /&gt;
==== Using the string helper (recommended) ====&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
// Assuming we had a context variable named &amp;quot;time&amp;quot; set to 1293876000.&lt;br /&gt;
{{#userdate}} {{time}}, {{#str}} strftimedate {{/str}} {{/userdate}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will ask the Moodle server for the string &amp;quot;strftimedate&amp;quot; and will use the value (which in this case is &amp;quot;%d %B %Y&amp;quot;)  to format the user date. So the resulting formatted timestamp from the userdate helper will be &amp;quot;01 January 2011&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
==== Using context variables ====&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
// Assuming we had a context variable named &amp;quot;time&amp;quot; set to 1293876000.&lt;br /&gt;
{{#userdate}} {{time}}, %A, %d %B %Y, %I:%M %p {{/userdate}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Will produce &amp;quot;Saturday, 01 January 2011, 10:00 AM&amp;quot;&lt;br /&gt;
&lt;br /&gt;
==== Using hardcoded values ====&lt;br /&gt;
&amp;lt;code xml&amp;gt;&lt;br /&gt;
{{#userdate}} 1293876000, %A, %d %B %Y, %I:%M %p {{/userdate}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Will produce &amp;quot;Saturday, 01 January 2011, 10:00 AM&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=== {{# shortentext }} (Moodle 3.3 onwards) ===&lt;br /&gt;
{{Moodle 3.3}}&lt;br /&gt;
This helper can be used to shorten a large amount of text to a specified length and will append a trailing ellipsis to signify that the text has been shortened. &lt;br /&gt;
&lt;br /&gt;
The algorithm will attempt to preserve words while shortening to text. Words, for the purposes of the helper, are considered to be groups of consecutive characters broken by a space or, in the case of a multi-byte character, after the completion of the multi-byte character (rather than in the middle of the character).&lt;br /&gt;
&lt;br /&gt;
It will also attempt to preserve HTML in the text by keeping the opening and closing tags. Only text within the tags will be considered when calculating how much should be truncated to reach the desired length.&lt;br /&gt;
&lt;br /&gt;
The helper takes two comma separated arguments. The first is the desired length and the second is the text to be shortened. Both can be provided as context variables.&lt;br /&gt;
&lt;br /&gt;
==== Plain text ====&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{{#shortentext}} 30, long text without any tags blah de blah blah blah what {{/shortentext}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Will produce:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
long text without any tags ...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== HTML text ====&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{{#shortentext}} 30, &amp;lt;div class=&#039;frog&#039;&amp;gt;&amp;lt;p&amp;gt;&amp;lt;blockquote&amp;gt;Long text with tags that will be chopped off but &amp;lt;b&amp;gt;should be added back again&amp;lt;/b&amp;gt;&amp;lt;/blockquote&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/p&amp;gt; {{/shortentext}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Will produce:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
&amp;lt;div class=&#039;frog&#039;&amp;gt;&amp;lt;p&amp;gt;&amp;lt;blockquote&amp;gt;Long text with tags that ...&amp;lt;/blockquote&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Multi-byte text ====&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
{{#shortentext}} 6, 𠮟𠮟𠮟𠮟𠮟𠮟𠮟𠮟𠮟𠮟𠮟𠮟 {{/shortentext}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Will produce:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
𠮟𠮟𠮟...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== How do I call a template from php? ==&lt;br /&gt;
&lt;br /&gt;
The templates in php are attached to the renderers. There is a renderer method &amp;quot;render_from_template($templatename, $context)&amp;quot; that does the trick.&lt;br /&gt;
&lt;br /&gt;
Note $context is nothing to do with normal Moodle &#039;contexts&#039;. It is the data passed to the template (the &#039;context&#039; mentioned in mustache documentation).&lt;br /&gt;
&lt;br /&gt;
== How do templates work with renderers? ==&lt;br /&gt;
&lt;br /&gt;
Extra care must be taken to ensure that the data passed to the context parameter is useful to the templating language. The template language cannot:&lt;br /&gt;
* Call functions&lt;br /&gt;
* Perform any boolean logic&lt;br /&gt;
* Render renderables&lt;br /&gt;
* Do capability checks&lt;br /&gt;
* Make DB queries&lt;br /&gt;
&lt;br /&gt;
So - I have &amp;quot;some&amp;quot; data in my renderable and some logic and HTML generation in my render method for that renderable - how do I refactor this to use a template?&lt;br /&gt;
&lt;br /&gt;
The first thing to note, is that you don&#039;t have to use a template if you don&#039;t want to. It just means that themers will still have to override your render method, instead of just overriding the template. But if you DO want to use a template, you will earn &amp;quot;cred&amp;quot; with themers, and you will be able to re-render parts of your interface from javascript in response to ajax requests without reloading the whole page (that&#039;s cool).&lt;br /&gt;
&lt;br /&gt;
There is a simple pattern to use to hook a template into a render method. If you make your renderable implement templatable as well as renderable - it will have to implement a new method &amp;quot;export_for_template(renderer_base $output)&amp;quot;. This method takes the data stored in the renderable and &amp;quot;flattens it&amp;quot; so it can be used in a template. If there is some nested data in the renderable (like other renderables) and they do not support templates, they can be &amp;quot;rendered&amp;quot; into the flat data structure using the renderer parameter. It should return an stdClass with properties that are only made of simple types: int, string, bool, float, stdClass or arrays of these types. Then the render method can updated to export the data and render it with the template.&lt;br /&gt;
&lt;br /&gt;
In the renderable:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 /**&lt;br /&gt;
     * Export this data so it can be used as the context for a mustache template.&lt;br /&gt;
     *&lt;br /&gt;
     * @return stdClass&lt;br /&gt;
     */&lt;br /&gt;
    public function export_for_template(renderer_base $output) {&lt;br /&gt;
        $data = new stdClass();&lt;br /&gt;
        $data-&amp;gt;canmanage = $this-&amp;gt;canmanage;&lt;br /&gt;
        $data-&amp;gt;things = array();&lt;br /&gt;
        foreach ($this-&amp;gt;things as $thing) {&lt;br /&gt;
            $data-&amp;gt;things[] = $thing-&amp;gt;to_record();&lt;br /&gt;
        }&lt;br /&gt;
        $data-&amp;gt;navigation = array();&lt;br /&gt;
        foreach ($this-&amp;gt;navigation as $button) {&lt;br /&gt;
            $data-&amp;gt;navigation[] = $output-&amp;gt;render($button);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return $data;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the renderer class:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    /**&lt;br /&gt;
     * Defer to template.&lt;br /&gt;
     *&lt;br /&gt;
     * @param mywidget $widget&lt;br /&gt;
     *&lt;br /&gt;
     * @return string HTML for the page&lt;br /&gt;
     */&lt;br /&gt;
    render(mywidget $widget) {&lt;br /&gt;
        $data = $widget-&amp;gt;export_for_template($this);&lt;br /&gt;
        return $this-&amp;gt;render_from_template(&#039;mywidget&#039;, $data);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== How to I override a template in my theme? ==&lt;br /&gt;
&lt;br /&gt;
Templates can be overridden a bit easier than overriding a renderer. First - find the template that you want to change. E.g. &amp;quot;mod/wiki/templates/ratingui.mustache&amp;quot;. Now, create a sub-folder under your themes &amp;quot;templates&amp;quot; directory with the component name of the plugin you are overriding. E.g &amp;quot;theme/timtam/templates/mod_wiki&amp;quot;. Finally, copy the ratingui.mustache file into the newly created &amp;quot;theme/timtam/templates/mod_wiki&amp;quot; and edit it. You should see your changes immediately if theme designer mode is on. Note: templates are cached just like CSS, so if you are not using theme designer mode you will need to purge all caches to see the latest version of an edited template. If the template you are overriding contains a documentation comment (see next section) it is recommended to remove it, it will still show the documentation in the template library.&lt;br /&gt;
&lt;br /&gt;
== Should I document my templates? ==&lt;br /&gt;
&lt;br /&gt;
Yes!!!! Theme designers need to know the limits of what they can expect to change without breaking anything. As a further benefit - your beautiful new template can be displayed in the &amp;quot;Template Library&amp;quot; tool shipped with Moodle. In order to provide nice documentation and examples for the Template Library, you should follow these conventions when documenting your template.&lt;br /&gt;
&lt;br /&gt;
=== Add a documentation comment to your template ===&lt;br /&gt;
Mustache comments look like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  {{! &lt;br /&gt;
   I am a comment.&lt;br /&gt;
   I can span multiple lines.&lt;br /&gt;
  }}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The template library will look for a mustache comment that contains this special marker as the documentation to display, and the source of an example context.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
@template component/templatename&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Useful things to include in the documentation for a template ====&lt;br /&gt;
===== Classes required for JS =====&lt;br /&gt;
This is a list of classes that are used by the javascript for this template. If removing a class from an element in the template will break the javascript, list it here.&lt;br /&gt;
&lt;br /&gt;
===== Data attributes required for JS =====&lt;br /&gt;
This is a list of data attributes (e.g. data-enhance=&amp;quot;true&amp;quot;) that are used by the javascript for this template. If removing a data attribute from an element in the template will break the javascript, list it here.&lt;br /&gt;
&lt;br /&gt;
===== Context variables required for this template =====&lt;br /&gt;
This is a description of the data that may be contained in the context that is passed to the template. Be explicit and document every attribute.&lt;br /&gt;
&lt;br /&gt;
===== Example context (JSON) =====&lt;br /&gt;
The Template Library will look for this data in your documentation comment as it allows it to render a &amp;quot;preview&amp;quot; of the template right in the Template Library. This is useful for theme designers to test all the available templates in their new theme to make sure they look nice in a new theme. It is also useful to make sure the template responds to different screen sizes, languages and devices. The format is a JSON-encoded object that is passed directly into the render method for this template. &lt;br /&gt;
&lt;br /&gt;
==== A full example ====&lt;br /&gt;
lib/templates/pix_icon.mustache&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
  {{!                                                                                                                                 &lt;br /&gt;
    This file is part of Moodle - http://moodle.org/                                                                                &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    Moodle is free software: you can redistribute it and/or modify                                                                  &lt;br /&gt;
    it under the terms of the GNU General Public License as published by                                                            &lt;br /&gt;
    the Free Software Foundation, either version 3 of the License, or                                                               &lt;br /&gt;
    (at your option) any later version.                                                                                             &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    Moodle is distributed in the hope that it will be useful,                                                                       &lt;br /&gt;
    but WITHOUT ANY WARRANTY; without even the implied warranty of                                                                  &lt;br /&gt;
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                                                   &lt;br /&gt;
    GNU General Public License for more details.                                                                                    &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    You should have received a copy of the GNU General Public License                                                               &lt;br /&gt;
    along with Moodle.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.                                                                 &lt;br /&gt;
  }}                                                                                                                                  &lt;br /&gt;
  {{!                                                                                                                                 &lt;br /&gt;
    @template core/pix_icon                                                                                                         &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    Moodle pix_icon template.                                                                                                       &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    The purpose of this template is to render a pix_icon.                                                                           &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    Classes required for JS:                                                                                                        &lt;br /&gt;
    * none                                                                                                                          &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    Data attributes required for JS:                                                                                                &lt;br /&gt;
    * none                                                                                                                          &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    Context variables required for this template:                                                                                   &lt;br /&gt;
    * attributes Array of name / value pairs.                                                                                       &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    Example context (json):                                                                                                         &lt;br /&gt;
    {                                                                                                                               &lt;br /&gt;
        &amp;quot;attributes&amp;quot;: [                                                                                                             &lt;br /&gt;
            { &amp;quot;name&amp;quot;: &amp;quot;src&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;http://moodle.com/wp-content/themes/moodle/images/logo-hat2.png&amp;quot; },                          &lt;br /&gt;
            { &amp;quot;name&amp;quot;: &amp;quot;class&amp;quot;, &amp;quot;value&amp;quot;: &amp;quot;iconsmall&amp;quot; }                                                                               &lt;br /&gt;
        ]                                                                                                                           &lt;br /&gt;
    }                                                                                                                               &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
  }}                                                                                                                                  &lt;br /&gt;
  &amp;lt;img {{#attributes}}{{name}}=&amp;quot;{{value}}&amp;quot; {{/attributes}}/&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Coding style for templates ==&lt;br /&gt;
This section documents some coding style guidelines to follow when writing templates. The reason for these guidelines is to promote consistency, and interoperability of the templates.&lt;br /&gt;
&lt;br /&gt;
=== Include GPL at the top of each template ===&lt;br /&gt;
&lt;br /&gt;
Templates are a form of code and it is appropriate to license them like any other code.&lt;br /&gt;
&lt;br /&gt;
===Include a documentation comment for each template===&lt;br /&gt;
&lt;br /&gt;
The exception is when you are overriding a template, if the documentation from the parent still applies, you do not need to copy it to the overridden template.&lt;br /&gt;
&lt;br /&gt;
===Use data-attributes for JS hooks===&lt;br /&gt;
&lt;br /&gt;
Data attributes are ideal for adding javascript hooks to templates because:&lt;br /&gt;
* Classes are meant for styling - theme designers should be able to change the classes at will without breaking any functionality.&lt;br /&gt;
* IDs must be unique in the page, but it is not possible to control how many times the same template might be included in the page.&lt;br /&gt;
* Data attributes can have meaningful names and can be efficiently queried with a selector&lt;br /&gt;
&lt;br /&gt;
===Avoid custom CSS for templates===&lt;br /&gt;
&lt;br /&gt;
This is not a hard rule, but a preference. We already have too much CSS in Moodle - where ever possible we should try and re-use the existing CSS instead of adding new CSS to support every new template.&lt;br /&gt;
&lt;br /&gt;
===Re-use core templates as much as possible===&lt;br /&gt;
&lt;br /&gt;
First we need to build the core set of reusable templates - but once that is in place we should always try to re-use those core templates to build interfaces. This will make Moodle more consistent, attractive and customisable.&lt;br /&gt;
&lt;br /&gt;
===Do use the CSS framework classes directly in the templates===&lt;br /&gt;
&lt;br /&gt;
We have bootstrap in core - so lets make the most of it. There is no problem using bootstrap classes in core templates, as long as the &amp;quot;base&amp;quot; theme is also tested, and an overridden template is added there if required.&lt;br /&gt;
&lt;br /&gt;
===Avoid IDs for styling or javascript===&lt;br /&gt;
&lt;br /&gt;
IDs should never evet be used for styling as they have a high CSS specificity, and so are hard to override. In addition, IDs should be unique in the page, which implies that a template could only be used once in a page. IDs are also not ideal for javascript, for the same reason (must be unique in a page).&lt;br /&gt;
&lt;br /&gt;
The only acceptable case to use an ID is you need to create a one to one connection between the JS and template. In this case use the uniqid helper to generate an ID that will not conflict with any other template on the page, and use it as part of the ID.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;{{uniqid}}-somethingspecific&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
    callFunction(&#039;{{uniqid}}-somethingspecific&#039;);&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Follow CSS coding style===&lt;br /&gt;
&lt;br /&gt;
https://docs.moodle.org/dev/CSS_coding_style&lt;br /&gt;
&lt;br /&gt;
Use hyphens as word-separators for class names. &lt;br /&gt;
Use lower case class names.&lt;br /&gt;
&lt;br /&gt;
===Wrap each template in one node with a classname that matches the template name===&lt;br /&gt;
&lt;br /&gt;
Generate a class name by combining the component and template names and separating words with underscore.&lt;br /&gt;
&lt;br /&gt;
e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;core_user_header&amp;quot;&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;br /&gt;
[[Category:Output]]&lt;br /&gt;
&lt;br /&gt;
===Iterating over php arrays in a mustache template===&lt;br /&gt;
&lt;br /&gt;
Mustache treats hashes and arrays differently because of cross language compatibility&lt;br /&gt;
In php arrays and hashes are the same, but mustache treats them differently&lt;br /&gt;
It decides a php array is a hash and will not iterate over it if it is non 0 indexed and/or has a gap in the key numbers&lt;br /&gt;
so in short&lt;br /&gt;
you need to&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$datafortemplate-&amp;gt;mylist =  array_values($myarraywithnonnumerickeys)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
you could also use &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$datafortemplate-&amp;gt;mylist = new ArrayIterator($myarraywithnonnumerickeys);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
BUT this fails when $myarraywithnonnumerickeys is empty and you try to use&lt;br /&gt;
&amp;lt;code html5&amp;gt;&lt;br /&gt;
{{#mylist}}&lt;br /&gt;
with an array iterator if mylist is empty this block will not run&lt;br /&gt;
{{/mylist}}&lt;br /&gt;
{{^mylist}}&lt;br /&gt;
with an array iterator mylist will not run this block either because it is not quite empty&lt;br /&gt;
{{/mylist}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Test for not empty array in a mustache template===&lt;br /&gt;
&lt;br /&gt;
Short answer you can&#039;t. Include a specific property in the context like &amp;quot;hasusers&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
In php &lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{{#users.0}} blah {{/users.0}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
will work but please don&#039;t use this syntax, it will break if the template is used in javascript AND it mucks up the context.&lt;br /&gt;
&lt;br /&gt;
In javascript&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{{#users.length}} blah {{/users.length}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
will work but please don&#039;t use this syntax, it will break if the template is used in php.&lt;br /&gt;
&lt;br /&gt;
===Squash whitespace in a template===&lt;br /&gt;
Sometimes whitespace is significant, for example inside a link it will show with an underline. If you want 2 mustache tags from separate lines to be rendered with no whitespace between them you can use mustache comments to squash the whitespace.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;a href=&amp;quot;blah&amp;quot;&amp;gt;{{!&lt;br /&gt;
}}{{icon}}{{!&lt;br /&gt;
}}{{name}}&amp;lt;/a&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== How do I write core templates? ==&lt;br /&gt;
Core templates should ideally be simple generic components that can be used within other templates to create more complex page layouts. They should be flexible enough for developers and themers to easily use without having to replace the template. The templates should attempt to encapsulate some core structure for the element as well as key classes while allowing the content to be easily overridden. Ultimately we want to avoid having duplicate HTML copied from template to template where possible, particularly if the HTML element has some classes associated with it.&lt;br /&gt;
&lt;br /&gt;
Mustache relies on variables to substitute context data into the template but unfortunately it&#039;s very unlikely that the the names of the context data will match what the template is expecting for all the places that the template might be used. So in order to allow easy extensibility and avoid having to duplicate templates just to rename the variables we can wrap them in block variables which would allow the template that is including our template to replace that variable with one from it&#039;s own context inline.&lt;br /&gt;
&lt;br /&gt;
There are a few key points to keep in mind when writing a core template:&lt;br /&gt;
* Consider how your template will actually be used. Try writing a test page that uses your template to help discover some of the assumptions you might have in the template.&lt;br /&gt;
* The example context you provide in the template is mostly just for showing the template in the template library and is likely not how your template will actually be used. Most uses of the template will have a different context all together.&lt;br /&gt;
* Try to enforce a core structure but avoid enforcing a specific context. Content should be overridable.&lt;br /&gt;
* Use block variables to indicate sections of your template that people are likely to want to change. Typically where they will be wanting to substitute in their own content.&lt;br /&gt;
* Try to keep any javascript that accompanies the template as decoupled from the HTML / CSS structure of the template as possible. Instead of relying on the existence of certain HTML elements or CSS classes it is generally better to leverage data-attributes which can be added to any element.&lt;br /&gt;
&lt;br /&gt;
=== An example: tabs ===&lt;br /&gt;
Let&#039;s go through an example to illustrate how you might build a core template. For the example we&#039;ll be building a tabs template, since it&#039;s a fairly complex component that requires the use of block variables and javascript.&lt;br /&gt;
&lt;br /&gt;
First we can create a basic template to get the general structure down, let&#039;s call it tabs. Here&#039;s what it might look like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;{{ uniqid }}-tab-container&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ul role=&amp;quot;tablist&amp;quot; class=&amp;quot;nav nav-tabs&amp;quot;&amp;gt;&lt;br /&gt;
		{{# tabs }}&lt;br /&gt;
			&amp;lt;li role=&amp;quot;tab&amp;quot;&lt;br /&gt;
					data-target=&amp;quot;{{ uniqid }}-{{ id }}&amp;quot;&lt;br /&gt;
					data-selected-class=&amp;quot;active&amp;quot;&lt;br /&gt;
					aria-controls=&amp;quot;{{ uniqid }}-{{ id }}&amp;quot;&lt;br /&gt;
					aria-selected=&amp;quot;false&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
				&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;{{{ name }}}&amp;lt;/a&amp;gt;&lt;br /&gt;
			&amp;lt;/li&amp;gt;&lt;br /&gt;
		{{/ tabs }}&lt;br /&gt;
	&amp;lt;/ul&amp;gt;&lt;br /&gt;
    &amp;lt;div class=&amp;quot;tab-content&amp;quot;&amp;gt;&lt;br /&gt;
		{{# tabs }}&lt;br /&gt;
			&amp;lt;div role=&amp;quot;tabpanel&amp;quot;&lt;br /&gt;
				class=&amp;quot;tab-pane&amp;quot;&lt;br /&gt;
				id=&amp;quot;{{ uniqid }}-{{ id }}&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
				{{{ content }}}&lt;br /&gt;
			&amp;lt;/div&amp;gt;&lt;br /&gt;
		{{/ tabs }}&lt;br /&gt;
	&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
    require([&#039;jquery&#039;,&#039;core/tabs&#039;], function($, tabs) {&lt;br /&gt;
&lt;br /&gt;
        var container = $(&amp;quot;#{{ uniqid }}-tab-container&amp;quot;);&lt;br /&gt;
        tabs.create(container);&lt;br /&gt;
    });&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The template requires a context that looks something like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;tabs&amp;quot;: [&lt;br /&gt;
		{&amp;quot;id&amp;quot;:&amp;quot;tab1&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;Tab 1&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;This is tab 1 content &amp;lt;a href=\&amp;quot;#\&amp;quot;&amp;gt;test&amp;lt;/a&amp;gt;&amp;quot;},&lt;br /&gt;
		{&amp;quot;id&amp;quot;:&amp;quot;tab2&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;Tab 2&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;This is tab 2 content &amp;lt;a href=\&amp;quot;#\&amp;quot;&amp;gt;test&amp;lt;/a&amp;gt;&amp;quot;},&lt;br /&gt;
		{&amp;quot;id&amp;quot;:&amp;quot;tab3&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;Tab 3&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;This is tab 3 content &amp;lt;a href=\&amp;quot;#\&amp;quot;&amp;gt;test&amp;lt;/a&amp;gt;&amp;quot;}&lt;br /&gt;
	]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The javascript required to power the tabs element (keyboard navigation, show / hide panels etc) is written as an AMD module and is included by the template. The javascript is a little too large to go through here, but some key points to consider when writing it are:&lt;br /&gt;
It should ideally be independent of the HTML structure, so if someone wants to completely rewrite the tabs to be different elements (e.g. buttons or a set of divs) then the same javascript can be used without needing to change it. In order to achieve this it is important to identify the key components of the template.&lt;br /&gt;
&lt;br /&gt;
In this case it is a tab list, a tab and it&#039;s content. One way to identify these components would be to inspect the structure of the DOM, for example you might say &amp;quot;find me the ul element&amp;quot; when looking for the tab list and then &amp;quot;find my the child li elements&amp;quot; to find the tabs. While this would work, it couples your javascript to the HTML structure and makes it difficult to change later. A different approach would be to use the element attributes, for example you might say &amp;quot;find my the element with the role &#039;tablist&#039;&amp;quot; to get the tab list and then &amp;quot;find me the elements with the role &#039;tab&#039;&amp;quot; to get the tabs. This allows the HTML structure to change without breaking the javascript (as long as the correct attributes are set, of course).&lt;br /&gt;
&lt;br /&gt;
Another point of consideration for this example is what class to apply to a tab when it is selected. It makes sense to just apply something like &amp;quot;active&amp;quot; in the javascript, but that once again couples it to a particular CSS framework which makes it more difficult to change without modifying the javascript. In this case I chose to add a data attribute to the element to indicate which class will be set when the tab is selected. This means the javascript doesn&#039;t have to guess what the appropriate class is, it can just get it from the template.&lt;br /&gt;
&lt;br /&gt;
Ok, so we&#039;ve got our basic template. It&#039;s time to use it! Let&#039;s say we want to create a simple user profile page that might show 2 tabs, the first tab will be the user&#039;s name and the second tab will be the user&#039;s email address (please excuse the contrived example).&lt;br /&gt;
&lt;br /&gt;
Here&#039;s what the page might look like:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt;&lt;br /&gt;
    &amp;lt;header&amp;gt;&amp;lt;title&amp;gt;User Profile&amp;lt;/title&amp;gt;&amp;lt;/header&amp;gt;&lt;br /&gt;
    &amp;lt;body&amp;gt;&lt;br /&gt;
        {{&amp;lt; core/tabs }}&lt;br /&gt;
    &amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That looks pretty simple! The only problem is, how do I get my content there? I would have to supply a context like this in order to display the tabs I want:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;tabs&amp;quot;: [&lt;br /&gt;
		{&amp;quot;id&amp;quot;:&amp;quot;tab1&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;Name&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;Your name is Mr. Test User.&amp;quot;},&lt;br /&gt;
		{&amp;quot;id&amp;quot;:&amp;quot;tab2&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;Email&amp;quot;,&amp;quot;content&amp;quot;:&amp;quot;Your email is testuser@example.com&amp;quot;},&lt;br /&gt;
	]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Let&#039;s assume that the context for this page doesn&#039;t match what the tabs template is expecting though (as will be the case most of the time). Let&#039;s assume the tabs template is being rendered with this context:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
	&amp;quot;name&amp;quot;:&amp;quot;Mr. Test User&amp;quot;,&lt;br /&gt;
	&amp;quot;email&amp;quot;:&amp;quot;testuser@example.com&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unfortunately, we&#039;ll almost certainly never have complete control over all of the contexts that our template will be rendered in which means we&#039;ll be expecting people to write new webservices to supply the same data in different formats every time they want to use a template. It becomes an unmanageable problem.&lt;br /&gt;
&lt;br /&gt;
Enter blocks! We can make the template more flexible by defining sections of the template that can be overriden when they are included. Pretty neat! This will allow us to enforce a certain core structure but not enforce a context on the template that is including the tabs.&lt;br /&gt;
&lt;br /&gt;
Let&#039;s have another go at that template, this time leveraging blocks:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;{{ uniqid }}-tab-container&amp;quot;&amp;gt;&lt;br /&gt;
	{{$ tabheader }}&lt;br /&gt;
		&amp;lt;ul role=&amp;quot;tablist&amp;quot; class=&amp;quot;nav nav-tabs&amp;quot;&amp;gt;&lt;br /&gt;
			{{$ tablist }}&lt;br /&gt;
				{{# tabs }}&lt;br /&gt;
					&amp;lt;li role=&amp;quot;tab&amp;quot;&lt;br /&gt;
							data-target=&amp;quot;{{ uniqid }}-{{ id }}&amp;quot;&lt;br /&gt;
							data-selected-class=&amp;quot;active&amp;quot;&lt;br /&gt;
							aria-controls=&amp;quot;{{ uniqid }}-{{ id }}&amp;quot;&lt;br /&gt;
							aria-selected=&amp;quot;false&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
						&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;{{{ name }}}&amp;lt;/a&amp;gt;&lt;br /&gt;
					&amp;lt;/li&amp;gt;&lt;br /&gt;
				{{/ tabs }}&lt;br /&gt;
			{{/ tablist }}&lt;br /&gt;
		&amp;lt;/ul&amp;gt;&lt;br /&gt;
	{{/ tabheader }}&lt;br /&gt;
	{{$ tabbody }}&lt;br /&gt;
		&amp;lt;div class=&amp;quot;tab-content&amp;quot;&amp;gt;&lt;br /&gt;
			{{$ tabcontent }}&lt;br /&gt;
				{{# tabs }}&lt;br /&gt;
					&amp;lt;div role=&amp;quot;tabpanel&amp;quot;&lt;br /&gt;
						class=&amp;quot;tab-pane&amp;quot;&lt;br /&gt;
						id=&amp;quot;{{ uniqid }}-{{ id }}&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
						{{{ content }}}&lt;br /&gt;
					&amp;lt;/div&amp;gt;&lt;br /&gt;
				{{/ tabs }}&lt;br /&gt;
			{{/ tabcontent }}&lt;br /&gt;
		&amp;lt;/div&amp;gt;&lt;br /&gt;
	{{/ tabbody }}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
    require([&#039;jquery&#039;,&#039;core/tabs&#039;], function($, tabs) {&lt;br /&gt;
&lt;br /&gt;
        var container = $(&amp;quot;#{{ uniqid }}-tab-container&amp;quot;);&lt;br /&gt;
        tabs.create(container);&lt;br /&gt;
    });&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A summary of what we&#039;ve changed:&lt;br /&gt;
* Added a $tabheader block around the tab list, in case someone wants to change the ul element to something else.&lt;br /&gt;
* Added a $tablist block around the group of tabs to allow them to be overriden on incldue.&lt;br /&gt;
* Added a $tabbody block around the content, in case someone wants to change the content elements from divs.&lt;br /&gt;
* Added a $tabcontent block around the tab variable for the content to allow the content to be overriden on inlcude.&lt;br /&gt;
&lt;br /&gt;
Now let&#039;s see what using this template looks like for your User Profile page:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt;&lt;br /&gt;
    &amp;lt;header&amp;gt;&amp;lt;title&amp;gt;User Profile&amp;lt;/title&amp;gt;&amp;lt;/header&amp;gt;&lt;br /&gt;
    &amp;lt;body&amp;gt;&lt;br /&gt;
        {{&amp;gt; core/tabs }}&lt;br /&gt;
			{{$ tablist }}&lt;br /&gt;
				&amp;lt;li role=&amp;quot;tab&amp;quot;&lt;br /&gt;
						data-target=&amp;quot;{{ uniqid }}-tab1&amp;quot;&lt;br /&gt;
						data-selected-class=&amp;quot;active&amp;quot;&lt;br /&gt;
						aria-controls=&amp;quot;{{ uniqid }}-tab1&amp;quot;&lt;br /&gt;
						aria-selected=&amp;quot;false&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
					&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;Name&amp;lt;/a&amp;gt;&lt;br /&gt;
				&amp;lt;/li&amp;gt;&lt;br /&gt;
				&amp;lt;li role=&amp;quot;tab&amp;quot;&lt;br /&gt;
						data-target=&amp;quot;{{ uniqid }}-tab2&amp;quot;&lt;br /&gt;
						data-selected-class=&amp;quot;active&amp;quot;&lt;br /&gt;
						aria-controls=&amp;quot;{{ uniqid }}-tab2&amp;quot;&lt;br /&gt;
						aria-selected=&amp;quot;false&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
					&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;Email&amp;lt;/a&amp;gt;&lt;br /&gt;
				&amp;lt;/li&amp;gt;&lt;br /&gt;
			{{/ tablist }}&lt;br /&gt;
			{{$ tabcontent }}&lt;br /&gt;
				&amp;lt;div role=&amp;quot;tabpanel&amp;quot;&lt;br /&gt;
					class=&amp;quot;tab-pane&amp;quot;&lt;br /&gt;
					id=&amp;quot;{{ uniqid }}-tab1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
					Your name is {{ name }}.&lt;br /&gt;
				&amp;lt;/div&amp;gt;&lt;br /&gt;
				&amp;lt;div role=&amp;quot;tabpanel&amp;quot;&lt;br /&gt;
					class=&amp;quot;tab-pane&amp;quot;&lt;br /&gt;
					id=&amp;quot;{{ uniqid }}-tab2&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
					Your email address is {{ email }}.&lt;br /&gt;
				&amp;lt;/div&amp;gt;&lt;br /&gt;
			{{/ tabcontent }}&lt;br /&gt;
		{{/ core/tabs }}&lt;br /&gt;
    &amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That looks a bit better! Now we&#039;ve been able to use the blocks to successfully change the template to use the context available to this page, we no longer need a &amp;quot;tabs&amp;quot; array with &amp;quot;name&amp;quot; and &amp;quot;content&amp;quot;. Even the javascript will continue to work because we&#039;ve kept the correct element attributes. &lt;br /&gt;
&lt;br /&gt;
We&#039;ve still got a slight problem though... In order to change the data for the template we&#039;ve had to copy &amp;amp; paste the HTML from the original template into our blocks as we do the override. While this works fine in this example, it means we don&#039;t quite get the encapsulation we want within the templates since we&#039;re leaking internal implementation details. If we ever wanted to change the CSS framework we use for Moodle (say from bootstrap 2 to boostrap 3 or 4) we&#039;d have to find all the places in the code where this tabs template is used and make sure that the HTML is correct in their block overrides.&lt;br /&gt;
&lt;br /&gt;
With that in mind, let&#039;s take one more pass at this template and see if we can improve it slightly again. This time we&#039;re doing to split the template out into 3 templates.&lt;br /&gt;
&lt;br /&gt;
tabs.mustache:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;div id=&amp;quot;{{ uniqid }}-tab-container&amp;quot;&amp;gt;&lt;br /&gt;
	{{$ tabheader }}&lt;br /&gt;
		&amp;lt;ul role=&amp;quot;tablist&amp;quot; class=&amp;quot;nav nav-tabs&amp;quot;&amp;gt;&lt;br /&gt;
			{{$ tablist }}&lt;br /&gt;
				{{# tabs }}&lt;br /&gt;
					{{&amp;gt; core/tab_header_item }}		&lt;br /&gt;
				{{/ tabs }}&lt;br /&gt;
			{{/ tablist }}&lt;br /&gt;
		&amp;lt;/ul&amp;gt;&lt;br /&gt;
	{{/ tabheader }}&lt;br /&gt;
	{{$ tabbody }}&lt;br /&gt;
		&amp;lt;div class=&amp;quot;tab-content&amp;quot;&amp;gt;&lt;br /&gt;
			{{$ tabcontent }}&lt;br /&gt;
				{{# tabs }}&lt;br /&gt;
					{{&amp;gt; core/tab_content_item }}&lt;br /&gt;
				{{/ tabs }}&lt;br /&gt;
			{{/ tabcontent }}&lt;br /&gt;
		&amp;lt;/div&amp;gt;&lt;br /&gt;
	{{/ tabbody }}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
    require([&#039;jquery&#039;,&#039;core/tabs&#039;], function($, tabs) {&lt;br /&gt;
&lt;br /&gt;
        var container = $(&amp;quot;#{{ uniqid }}-tab-container&amp;quot;);&lt;br /&gt;
        tabs.create(container);&lt;br /&gt;
    });&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
tab_header_item.mustache&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;li role=&amp;quot;tab&amp;quot;&lt;br /&gt;
		data-selected-class=&amp;quot;active&amp;quot;&lt;br /&gt;
		aria-selected=&amp;quot;false&amp;quot; tabindex=&amp;quot;-1&amp;quot;&amp;gt;&lt;br /&gt;
&lt;br /&gt;
	&amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;{{$ tabname }}{{{ name }}}{{/ tabname }}&amp;lt;/a&amp;gt;&lt;br /&gt;
&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
tab_content_item.mustache&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;div role=&amp;quot;tabpanel&amp;quot;&lt;br /&gt;
	class=&amp;quot;tab-pane&amp;quot;&lt;br /&gt;
&lt;br /&gt;
	{{$ tabpanelcontent }}{{{ content }}}{{/ tabpanelcontent }}&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A summary of the changes:&lt;br /&gt;
* Split the template into 3, moving the tab into it&#039;s own template and the content into it&#039;s own and then including them in the tabs template.&lt;br /&gt;
* Removed the ids from the tabs and content. The javascript would be updating to assign these ids at runtime so that they don&#039;t need to be provided as part of the template context.&lt;br /&gt;
* Added a $tabname block for in the tab_header_item template to make the name flexible on import.&lt;br /&gt;
* Added a $tabpanelcontant block in the tab_content_item template to make the content flexible on import.&lt;br /&gt;
&lt;br /&gt;
Cool, so let&#039;s see what that looks like in our example now:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;html&amp;gt;&lt;br /&gt;
    &amp;lt;header&amp;gt;&amp;lt;title&amp;gt;User Profile&amp;lt;/title&amp;gt;&amp;lt;/header&amp;gt;&lt;br /&gt;
    &amp;lt;body&amp;gt;&lt;br /&gt;
        {{&amp;gt; core/tabs }}&lt;br /&gt;
			{{$ tablist }}&lt;br /&gt;
				{{&amp;lt; core/tab_header_item }}&lt;br /&gt;
					{{$ tabname }}Name{{/ tabname }}&lt;br /&gt;
				{{/ core/tab_header_item }}&lt;br /&gt;
				{{&amp;lt; core/tab_header_item }}&lt;br /&gt;
					{{$ tabname }}Email{{/ tabname }}&lt;br /&gt;
				{{/ core/tab_header_item }}&lt;br /&gt;
			{{/ tablist }}&lt;br /&gt;
			{{$ tabcontent }}&lt;br /&gt;
				{{&amp;lt; core/tab_content_item }}&lt;br /&gt;
					{{$ tabpanelcontent }}Your name is {{ name }}.{{/ tabpanelcontent }}&lt;br /&gt;
				{{/ core/tab_content_item }}&lt;br /&gt;
				{{&amp;lt; core/tab_content_item }}&lt;br /&gt;
					{{$ tabpanelcontent }}Your email address is {{ email }}.{{/ tabpanelcontent }}&lt;br /&gt;
				{{/ core/tab_content_item }}&lt;br /&gt;
			{{/ tabcontent }}&lt;br /&gt;
		{{/ core/tabs }}&lt;br /&gt;
    &amp;lt;/body&amp;gt;&lt;br /&gt;
&amp;lt;/html&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And we&#039;re done! After making the changes above we&#039;ve been able to keep the benefits of the previous change to allow the context changes but we&#039;ve also removed the need to copy &amp;amp; paste the HTML everywhere. Instead we&#039;re able to use the child templates with a few additional blocks defined to get the content in there.&lt;br /&gt;
&lt;br /&gt;
Now if we want to change tabs HTML or CSS frameworks we can just change the core tabs templates and this page will receive the updates for free.&lt;/div&gt;</summary>
		<author><name>Colin.bernard</name></author>
	</entry>
</feed>