<?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=Marxjohnson</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=Marxjohnson"/>
	<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/Special:Contributions/Marxjohnson"/>
	<updated>2026-08-16T17:41:23Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User:Mark_Johnson&amp;diff=64037</id>
		<title>User:Mark Johnson</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User:Mark_Johnson&amp;diff=64037"/>
		<updated>2023-11-17T14:21:25Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* Mark Johnson&amp;#039;s pages */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Mark Johnson&#039;s pages ===&lt;br /&gt;
Notes, docs, etc.&lt;br /&gt;
* [[User:Mark_Johnson/Namespaces]]&lt;br /&gt;
* [[User:Mark_Johnson/Output_Components_Cheatsheet]]&lt;br /&gt;
* [[User:Mark_Johnson/Javascript]]&lt;br /&gt;
* [[User:Mark_Johnson/Mforms_and_AJAX]]&lt;br /&gt;
* [[User:Mark_Johnson/Combined_javascript_style]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User:Mark_Johnson&amp;diff=64036</id>
		<title>User:Mark Johnson</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User:Mark_Johnson&amp;diff=64036"/>
		<updated>2023-11-17T14:21:09Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* Mark Johnson&amp;#039;s pages */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Mark Johnson&#039;s pages ===&lt;br /&gt;
Notes, docs, etc.&lt;br /&gt;
&lt;br /&gt;
* [[User:Mark_Johnson/Namespaces]]&lt;br /&gt;
* [[User:Mark_Johnson/Output_Components_Cheatsheet]]&lt;br /&gt;
* [[User:Mark_Johnson/Javascript]]&lt;br /&gt;
* [[User:Mark_Johnson/Mforms_and_AJAX]]&lt;br /&gt;
* [[User:Mark_Johnson/Combined_javascript_style]]&lt;br /&gt;
* [[User:Mark_Johnson/Security]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=61888</id>
		<title>Writing acceptance tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=61888"/>
		<updated>2022-03-22T15:22:29Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Fix date selector example in human-readable and relative dates&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
This documentation gives some hints on writing behat tests for Moodle core, and for plugins. The focus of the documentation is on behat tests for plugins. Behat Features and Scenarios are written in a natural language, and should&lt;br /&gt;
describe how a user would interact with Moodle.&lt;br /&gt;
&lt;br /&gt;
Each test consists of several stages which are categorised by the terms &#039;&#039;&#039;Given&#039;&#039;&#039;, &#039;&#039;&#039;When&#039;&#039;&#039;, and &#039;&#039;&#039;Then&#039;&#039;&#039;:&lt;br /&gt;
*&#039;&#039;&#039;Given&#039;&#039;&#039;: These steps allow you to perform a test set-up. Typically Given steps are used to set configuration, create users, courses and plugin instances, and generally prepare the site for testing&lt;br /&gt;
*&#039;&#039;&#039;When&#039;&#039;&#039;: When steps are used to get the test environment to the point at which you wish to test the conditions. This may include logging in, and then performing a range of actions like submitting an assignment and grading it for example&lt;br /&gt;
*&#039;&#039;&#039;Then&#039;&#039;&#039;: Then steps are used to check that your plugin behaved as expected. They typically check very simple things like if certain elements or text are visible or not. For example after submitting an assignment in the When stage, you may have a step which checks that a notice was shown to state that the submission was successful.&lt;br /&gt;
These three stages match the standard [http://xunitpatterns.com/Four%20Phase%20Test.html Four-phase test pattern]. The fourth phase is &#039;tear-down&#039; which is performed by Moodle between each test and does not need to be explicitly defined in your test.&lt;br /&gt;
&lt;br /&gt;
Note: Each test should have only one use of &#039;&#039;&#039;Given&#039;&#039;&#039;, &#039;&#039;&#039;When&#039;&#039;&#039;, and &#039;&#039;&#039;Then&#039;&#039;&#039;.&lt;br /&gt;
See MDLSITE-3778 for information and the policy decision on why you should not have multiple Given, When, and Then steps.&lt;br /&gt;
&lt;br /&gt;
Where you have several Given, When, and Then steps you should use the words &#039;&#039;&#039;And&#039;&#039;&#039;, and &#039;&#039;&#039;But&#039;&#039;&#039;, for example:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
Given the following user exists:&lt;br /&gt;
  | username   | ccolon             |&lt;br /&gt;
  | First name | Colin              |&lt;br /&gt;
  | Last name  | Colon              |&lt;br /&gt;
  | email      | ccolon@example.com |&lt;br /&gt;
And the following course exists:&lt;br /&gt;
  | Name      | Jump Judging (Level 1) |&lt;br /&gt;
  | Shortname | sjea1                  |&lt;br /&gt;
When I log in as &amp;quot;ccolon&amp;quot;&lt;br /&gt;
And I navigate to &amp;quot;Site home &amp;gt; Jump Judging (Level 1)&amp;quot;&lt;br /&gt;
Then I should see &amp;quot;You are not enrolled in this course&amp;quot;&lt;br /&gt;
But I should see &amp;quot;Enrol now&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
To initialize and run your tests, please follow the instructions of [[Running_acceptance_test]].&lt;br /&gt;
==Create your own tests==&lt;br /&gt;
Behat tests are located within the directory tests/behat of your plugin.&lt;br /&gt;
The different tests are defined in files with the ending *.feature.&lt;br /&gt;
First, you have to define the header of your test:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
@mod @mod_yourplugin @javascript&lt;br /&gt;
Feature: Here comes a description of your user story.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The tags on top of the feature description can be used to select specific test cases when running the tests.&lt;br /&gt;
The &#039;@javascript&#039; tag should only be used, if javascript is needed to execute your test. This is dependent on the step you will use in your definition.&lt;br /&gt;
Javascript tests are usually much slower than tests executed without javascript.&lt;br /&gt;
&lt;br /&gt;
Afterwards you can specify a scenario:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
@javascript&lt;br /&gt;
  Scenario: Description of your scenario, which you want to test.&lt;br /&gt;
    When I log in as &amp;quot;student1&amp;quot;&lt;br /&gt;
    And I am on &amp;quot;Course 1&amp;quot; course homepage&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Again you can define specific tags. Afterwards you write the steps, which should be executed during your test.&lt;br /&gt;
====Multiple Scenarios====&lt;br /&gt;
You can have an arbitrary amount of scenarios within a test. Please make sure they all belong to the same feature.&lt;br /&gt;
If you have certain steps, which should be executed for every scenario of a feature, you can define them using a background:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
Background:&lt;br /&gt;
    Given the following &amp;quot;courses&amp;quot; exist:&lt;br /&gt;
      | fullname | shortname | category | groupmode |&lt;br /&gt;
      | Course 1 | C1        | 0        | 1         |&lt;br /&gt;
    And the following &amp;quot;users&amp;quot; exist:&lt;br /&gt;
      | username | firstname | lastname | email |&lt;br /&gt;
      | teacher1 | Theo | Teacher | teacher1@example.com |&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This is usually used, to define the different &#039;&#039;&#039;GIVEN&#039;&#039;&#039; steps.&lt;br /&gt;
====Use existing steps====&lt;br /&gt;
There are different ways how to effectively browse the available existing steps:&lt;br /&gt;
======Moodle Administration======&lt;br /&gt;
Moodle offers within its administration menu under Site Administration &amp;gt; Development &amp;gt; Acceptance Testing a complete and searchable list of all available step definitions.&lt;br /&gt;
However, make sure you installed the behat test site first!&lt;br /&gt;
======PhpStorm======&lt;br /&gt;
In PhpStorm or IntelliJ you can install the behat extension. Then you get auto completions within feature files, which helps a lot during behat test development.&lt;br /&gt;
====Providing values to steps====&lt;br /&gt;
Most of the steps requires values, there are methods to provide values to steps, the method depends on the step specification.&lt;br /&gt;
*&#039;&#039;&#039;A string/text&#039;&#039;&#039;; is the most common case, the texts are wrapped between double quotes (&amp;quot; character) you have to replace the info about the expected value for your value; for example something like &#039;&#039;&#039;I press &amp;quot;BUTTON_STRING&amp;quot;&#039;&#039;&#039; should become &#039;&#039;&#039;I press &amp;quot;Save and return to course&amp;quot;&#039;&#039;&#039;. If you want to add a string which contains a &amp;quot; character, you can escape it with \&amp;quot;, for example &#039;&#039;&#039;I fill the &amp;quot;Name&amp;quot; field with &amp;quot;Alan alias \&amp;quot;the legend\&amp;quot;&amp;quot;&#039;&#039;&#039;. You can identify this steps because they ends with &#039;&#039;&#039;_STRING&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A number&#039;&#039;&#039;; some steps requires numbers as values, to be more specific an undetermined number of digits from 0 to 9 (Natural numbers + 0) you can identify them because the expected value info string ends with &#039;&#039;&#039;_NUMBER&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A table&#039;&#039;&#039;; is a relation between values, the most common use of it is to fill forms. The steps which requires tables are easily identifiable because they finish with &#039;&#039;&#039;:&#039;&#039;&#039; The steps description gives info about what the table columns must contain, for example &#039;&#039;&#039;Fills a moodle form with field/value data&#039;&#039;&#039;. Here you don&#039;t need to escape the double quotes if you want to include them as part of the value.&lt;br /&gt;
* &#039;&#039;&#039;A PyString&#039;&#039;&#039;; is a multiline string, most commonly used to fill out forms when a newline is required. Like steps with tables, steps which require PyStrings will end with &amp;quot;:&amp;quot;&lt;br /&gt;
*&#039;&#039;&#039;A field value&#039;&#039;&#039;; There are many different field types, if an argument requires a field value the expected value will depend on the field type:&lt;br /&gt;
**Text-based fields: It expects the text. This includes textareas, input type text, input type password...&lt;br /&gt;
**Checkbox: It expects 1 to check and for checked and &amp;quot;&amp;quot; to uncheck or for unchecked&lt;br /&gt;
**Select: It expects the option text or the option value. In case you interact with a multi-select you should specify the options separating them with commas. For example: &#039;&#039;&#039;option1, option2, option3&#039;&#039;&#039;&lt;br /&gt;
**Radio: The text of the radio option&lt;br /&gt;
*&#039;&#039;&#039;A selector&#039;&#039;&#039;; there are steps that can be used with different kinds of elements, for example &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;link&amp;quot;&#039;&#039;&#039; or &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;button&amp;quot;&#039;&#039;&#039; this is a closed list of elements, they always works together with another argument, where you specify the locator (eg. the link text in a link) In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** field - for searching a field by its id, name, value or label&lt;br /&gt;
**link - for searching a link by its href, id, title, img alt or value&lt;br /&gt;
**button - for searching a button by its name, id, value, img alt or title&lt;br /&gt;
**link_or_button - for searching for both, links and buttons&lt;br /&gt;
** select - for searching a select field by its id, name or label&lt;br /&gt;
** checkbox - for searching a checkbox by its id, name, or label&lt;br /&gt;
**radio - for searching a radio button by its id, name, or label&lt;br /&gt;
**file - for searching a file input by its id, name, or label&lt;br /&gt;
**optgroup - for searching optgroup by its label&lt;br /&gt;
**option - for searching an option by its content&lt;br /&gt;
**dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
**filemanager - for searching a filemanager by it&#039;s id or label&lt;br /&gt;
**block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
**activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
**region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
**table_row - for searching a table row which contains the specified text&lt;br /&gt;
**table - for searching a table by its id or caption&lt;br /&gt;
** icon - for searching an icon by its title&lt;br /&gt;
**fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
**xpath_element - for searching an element by its XPath&lt;br /&gt;
*&#039;&#039;&#039;A text selector&#039;&#039;&#039;; similar to a selector but those are the elements that returns an area of the DOM, they are useful in steps following the format &#039;&#039;&#039;... in the &amp;quot;Community finder&amp;quot; &amp;quot;block&amp;quot;&#039;&#039;&#039; where you are clicking or looking for some text inside a specific area. In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
**block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
**activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
**region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
**table_row - for searching a table row which contains the specified text&lt;br /&gt;
**table - for searching a table by its id or caption&lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** list_item - for searching a list item which contains the specified text&lt;br /&gt;
**css_element - for searching an element by its CSS selector&lt;br /&gt;
**xpath_element - for searching an element by its XPath&lt;br /&gt;
======Checking table values ======&lt;br /&gt;
You can check if specific value exists or not in a table row/column by using:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
Then &amp;quot;STRING_IN_ROW&amp;quot; row &amp;quot;COLUMN_HEADER&amp;quot; column of &amp;quot;TABLE_ID&amp;quot; table should contain &amp;quot;VALUE_TO_CHECK&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
or&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
Then the following should exist in the &amp;quot;TABLE_ID&amp;quot; table:&lt;br /&gt;
    | COLUMN_HEADER1 | COLUMN_HEADER2 |&lt;br /&gt;
    | VALUE_IN_ROW_1 | VALUE_IN_ROW_1 |&lt;br /&gt;
    | VALUE_IN_ROW_2 | VALUE_IN_ROW_2 |&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Advanced use cases====&lt;br /&gt;
Most of the time the usage of existing step definitions is straight forward. However, there are some exceptions were it might get complicated. Some of them are listed here:&lt;br /&gt;
======Uploading files======&lt;br /&gt;
Note than some tests requires files to be uploaded, in this case&lt;br /&gt;
*The &#039;&#039;&#039;I upload &amp;quot;FILEPATH_STRING&amp;quot; file to &amp;quot;FILEPICKER_FIELD_STRING&amp;quot; filepicker&#039;&#039;&#039; step can be used when located in the form page&lt;br /&gt;
* The file to upload should be included along with the Moodle codebase in COMPONENTNAME/tests/fixtures/*&lt;br /&gt;
*The file to upload is specified by it&#039;s path, which should be relative to the codebase root (&#039;&#039;&#039;lib/tests/fixtures/users.csv&#039;&#039;&#039; for example)&lt;br /&gt;
*&#039;&#039;&#039;/&#039;&#039;&#039; should be used as directory separator and the file names can not include this &#039;&#039;&#039;/&#039;&#039;&#039; character as all of them would be converted to the OS-dependant directory separator to maintain the compatibility with Windows systems.&lt;br /&gt;
*The scenarios that includes files uploading should be tagged using the &#039;&#039;&#039;@_file_upload&#039;&#039;&#039; tag&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
@editor @editor_atto @atto @atto_media @_file_upload&lt;br /&gt;
Feature: Add media to Atto&lt;br /&gt;
  To write rich text - I need to add media.&lt;br /&gt;
&lt;br /&gt;
  Background:&lt;br /&gt;
    Given I log in as &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I follow &amp;quot;Manage private files...&amp;quot;&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.webm&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.mp4&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.png&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-en.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-sv.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I click on &amp;quot;Save changes&amp;quot; &amp;quot;button&amp;quot;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Field groups======&lt;br /&gt;
This section describes how you can use the step definitions&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
...&lt;br /&gt;
When I set the field &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot; to &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
for field groups. Examples for such field groups are the duration field or the date_time_selector. These are not displayed as one single input field within the front-end but consist of multiple input fields within one row.&lt;br /&gt;
You can access each single input field of a group using &lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
identifierOfYourField[keyOfTheSpecificInput]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Examples would be:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   21   |&lt;br /&gt;
  | myDate[month]           |   12   |&lt;br /&gt;
  | myDate[hour]            |   14   |&lt;br /&gt;
  | myDuration[number]      |   10   |&lt;br /&gt;
  | myDuration[unit]        | days   |&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
======Human-readable and relative dates======&lt;br /&gt;
When testing plugins with deadlines, for instance for submissions, it is often necessary to set certain time values to dates relative to today.&lt;br /&gt;
You can specify a relative time enclosed within two ## blocks. For example:&lt;br /&gt;
*&amp;lt;nowiki&amp;gt;## yesterday ##&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
*&amp;lt;nowiki&amp;gt;## 2 days ago ##&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
You can use everything according to http://php.net/manual/en/datetime.formats.php.&lt;br /&gt;
&lt;br /&gt;
Especially useful are the relative formats from: http://php.net/manual/en/datetime.formats.relative.php&lt;br /&gt;
&lt;br /&gt;
Additionally, you can specify a format you want the date to be returned into:&lt;br /&gt;
*&amp;lt;nowiki&amp;gt;## yesterday ## myformat ##&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
These formats can be used as outlined in http://php.net/manual/en/function.date.php.&lt;br /&gt;
This can be combined with the field groups:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]   | ##yesterday##d## |&lt;br /&gt;
  | myDate[month] | ##yesterday##F## |&lt;br /&gt;
  | myDate[year]  | ##yesterday##Y## |&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
====Writing your own steps====&lt;br /&gt;
Sometimes, you will need to set up data that is specific to your plugin, or perform steps that are specific to your plugin&#039;s UI. In this case it may be necessary to [[Writing_new_acceptance_test_step_definitions|write new step definitions]], but the short version is that you define new steps as PHP methods with a special annotation inside a class called &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; inside tests/behat/behat_plugintype_plugingname.php in your plugin.&lt;br /&gt;
&lt;br /&gt;
As well as creating completely new steps, you can also extend some of the standard steps:&lt;br /&gt;
=====Custom selectors (&amp;lt;tt&amp;gt;... in the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot;&amp;lt;/tt&amp;gt;)=====&lt;br /&gt;
There are a load of different steps which can refer to specific items on-screen, for example&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
And I click on &amp;quot;Submit all and finish&amp;quot; &amp;quot;button&amp;quot; in the &amp;quot;Confirmation&amp;quot; &amp;quot;dialogue&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Here, &#039;button&#039; and &#039;dialogue&#039; are examples of selectors, and &#039;Submit all and finish&#039; and &#039;Confirmation&#039; are the locators which say which button or dialogue it is. When the test runs, this gets converted to an XPath expression, which is what the Behat system acutally uses to locate the right element on the page.&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, you can define new types of selector (for example &amp;lt;tt&amp;gt;core_message &amp;gt; Message&amp;lt;/tt&amp;gt;) by implementing functions like &amp;lt;tt&amp;gt;behat_component_named_selector&amp;lt;/tt&amp;gt; in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class. The detailed instructions for how to do this are in [https://github.com/moodle/moodle/blob/33da028c27607354981cd8e62ecabb7b973c6637/lib/behat/behat_base.php#L1111 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
The reasons you might want to do this are:&lt;br /&gt;
*It makes your tests easier to read, which makes it easier to be sure that the test is testing the right thing, and being able to read the tests helps people understand your features.&lt;br /&gt;
*If the HTML structure you output changes, then you only need to update the selector definition in one place.&lt;br /&gt;
=====Custom navigation targets (&amp;lt;tt&amp;gt;And I am on the &amp;quot;...&amp;quot; &amp;quot;...&amp;quot; page&amp;lt;/tt&amp;gt;)=====&lt;br /&gt;
From Moodle 3.8 onwards,there are two related steps:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
Given I am on the &amp;quot;Quiz 1&amp;quot; &amp;quot;mod_quiz &amp;gt; View&amp;quot; page logged in as &amp;quot;manager&amp;quot;&lt;br /&gt;
Given I am on the &amp;quot;C1&amp;quot; &amp;quot;Course&amp;quot; page&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
To make this work, in your plugin&#039;s &amp;lt;tt&amp;gt;behat_plugintype_plugingname&amp;lt;/tt&amp;gt; class, you need to implement the functions &amp;lt;tt&amp;gt;resolve_page_url&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;resolve_page_instance_url&amp;lt;/tt&amp;gt; methods. Once again, the detailed instructions about how this works are given in [https://github.com/moodle/moodle/blob/a0fc902eb184cd4097c8ab453ddc57964cd2dbd4/lib/behat/behat_base.php#L1093 the PHPdoc comments on the base class].&lt;br /&gt;
&lt;br /&gt;
There are two reasons why it is good to use these steps:&lt;br /&gt;
*You are trying to test that your feature works, not Moodle navigation. In the pase we have had many occasions when Moodle navigation changed, and lots of tests failed and had to be fixed. It is better for your tests to start on your feature. (Except, perhpas, it might be appropriate to have one test for the expected method for users to navigate to your feature.)&lt;br /&gt;
*It is much faster because you load fewer irrelevant pages, and in particular the normal loggi step leaves you on the Dashboard page, which is &#039;&#039;&#039;very&#039;&#039;&#039; slow to load.&lt;br /&gt;
===== Custom entity generators (&amp;lt;tt&amp;gt;And the following &amp;quot;...&amp;quot; exist:&amp;lt;/tt&amp;gt;)=====&lt;br /&gt;
From Moodle 3.8 onwards, it is possible to extend the &#039;&#039;Given the following &amp;quot;entites&amp;quot; exist&#039;&#039; step to support your plugin&#039;s data generators. This avoids having to write new whole&lt;br /&gt;
new behat step definitions for your plugin, and allows you to re-use data generators between PHPUnit and Behat tests.&lt;br /&gt;
&lt;br /&gt;
Full documentation of this process and all available options can be found in the [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/lib/behat/classes/behat_generator_base.php#L33 PHPDoc for behat_generator_base]. A core example of this can be found in [https://github.com/moodle/moodle/tree/master/mod/quiz/tests/generator /mod/quiz/tests/generator] and [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/mod/quiz/tests/behat/quiz_reset.feature#L51 quiz_reset.feature]. What follows is a simple example.&lt;br /&gt;
&lt;br /&gt;
To begin, you need a [[Writing_PHPUnit_tests#Generators|generator]] in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/lib.php. If you are generating a type of entity called &amp;quot;thing&amp;quot;, your generator will need a method called create_thing, which accepts an object:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
class local_myplugin_generator extends component_generator_base {&lt;br /&gt;
    public function create_thing($thing) {&lt;br /&gt;
        global $DB;&lt;br /&gt;
        $DB-&amp;gt;insert_record(&#039;local_myplugin_things&#039;, $thing);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Next, you will need to define your behat generator in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/behat_&#039;&#039;your_plugin&#039;&#039;_generator.php, with the method get_createable_entitites():&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;php&amp;quot;&amp;gt;&lt;br /&gt;
class behat_local_myplugin_generator extends behat_generator_base {&lt;br /&gt;
&lt;br /&gt;
    protected function get_creatable_entities(): array {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;things&#039; =&amp;gt; [&lt;br /&gt;
                &#039;datagenerator&#039; =&amp;gt; &#039;thing&#039;,&lt;br /&gt;
                &#039;required&#039; =&amp;gt; [&#039;name&#039;]&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The &#039;datagenerator&#039; value refers to the method in the generator class that we are calling, in this case &#039;&#039;create_thing()&#039;&#039;. The outer array key is the entity name we will use in the behat step, in this case &#039;&#039;Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Now, in your behat test, you can have a step like this, which will generate 2 &#039;&#039;things&#039;&#039;, the first with the name &amp;quot;thing1&amp;quot; and the second with the name &amp;quot;thing2&amp;quot;.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;gherkin&amp;quot;&amp;gt;&lt;br /&gt;
Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist:&lt;br /&gt;
  | name   |&lt;br /&gt;
  | thing1 |&lt;br /&gt;
  | thing2 |&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==Good practice==&lt;br /&gt;
===Test one thing per scenario===&lt;br /&gt;
The ideal that you should strive for, is that each scenario tests just one specific bit of functionality. Therefore, if one test fails, the scenario name should tell you exactly what the bug is. Also, any bug should cause just one scenario to fail, not lots of unrelated ones. If you can achieve this, then the idea is that it minimises the time from seeing a test fail to having fixed the bug that was detected. Of course, this ideal is not always achievable, but in my experience it is worth striving for.&lt;br /&gt;
&lt;br /&gt;
Note that this also implies that the Given, When and Then keywords should be used only once per scenario.&lt;br /&gt;
===Set-up (Given) should not use the UI===&lt;br /&gt;
The setup is not what you are really testing here. Therefore, it should be as quick and reliable as possible. The way to achieve this is with steps like &amp;lt;tt&amp;gt;And the following &amp;quot;Thing&amp;quot; exist:&amp;lt;/tt&amp;gt; which directly insert the data into the database. If necessary, write extra steps for your plugin to setup the things you need.&lt;br /&gt;
===Don&#039;t use XPath or CSS selectors - fix your Accessibility bugs===&lt;br /&gt;
If, the only way you can identify something in the page that you want to manipulate is with a step like &amp;lt;tt&amp;gt;I set the field with xpath &amp;quot;//textarea[contains(@name, &#039;answer&#039;)]&amp;quot; to &amp;quot;frog&amp;quot;&amp;lt;/tt&amp;gt;, then this is probably the sign that you have an Accessibility bug, because Behat accesses the page very like a screen-reader user would.&lt;br /&gt;
&lt;br /&gt;
You should be able to refer to things with steps like &amp;lt;tt&amp;gt;I set the field &amp;quot;Answer&amp;quot; to &amp;quot;frog&amp;quot;&#039;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;I click on &amp;quot;True&amp;quot; &amp;quot;radio&amp;quot; in the &amp;quot;First question&amp;quot; &amp;quot;question&amp;quot;&amp;lt;/tt&amp;gt;. If not, you should probably think about fixing the accessibility bug, rather than resorting to unreadable selectors in your Behat test.&lt;br /&gt;
===When you define more steps in your plugin, make it clear they come from your plugin===&lt;br /&gt;
When defining new Step definitions in your plugin, try to make sure the step name identifies it as belonging to your plugin. So, don&#039;t make a step called &amp;lt;tt&amp;gt;I disable UI plugins&amp;lt;/tt&amp;gt;. Call it something like &amp;lt;tt&amp;gt;I disable UI plugins in the CodeRunner question type&amp;lt;/tt&amp;gt;.&lt;br /&gt;
===PHPDoc comments to map scenario steps===&lt;br /&gt;
PHPDoc style comments before functions can be used to map to your .scenario files. Read more about this here&lt;br /&gt;
&lt;br /&gt;
https://behat.org/en/latest/user_guide/context/definitions.html&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
When you define more steps in your plugin, make it clear they come from your plugin&lt;br /&gt;
[[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=61513</id>
		<title>Setting up your development environment for the Moodle App</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=61513"/>
		<updated>2021-11-17T08:49:22Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Update windows npm start workaround for app 3.9.5&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle App (Ionic 5)}}&lt;br /&gt;
The structure of this page is the following:&lt;br /&gt;
* The first part, up to the point where you get the &amp;lt;code&amp;gt;npm start&amp;lt;/code&amp;gt; command to work, includes the basics of what you need to work on the app.&lt;br /&gt;
* The second part indicates how to build the app to run it on a native device (or emulator).&lt;br /&gt;
* The third part includes a list of troubleshooting advice. If you encounter a problem that is not already listed, please consider adding it.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Most of your development can happen on a browser, you only need to use an emulator if you have to work on native functionality.&lt;br /&gt;
&lt;br /&gt;
If you are just [[Moodle App Plugins Development Guide|adding mobile support to plugins]], you probably don&#039;t need to build the app yourself and you can skip reading this page.&lt;br /&gt;
== Requirements ==&lt;br /&gt;
===Install a browser for development===&lt;br /&gt;
Most of the time we recommend that you use a browser for development; the app will work in any Chromium-based browser. We recommend using the Chromium browser (an open source alternative to Google Chrome). You can get it from the [https://www.chromium.org/getting-involved/download-chromium official download page].&lt;br /&gt;
&lt;br /&gt;
To learn more about using a browser for development, and why it needs to be Chromium-based, read the [[Using the Moodle App in a browser]] page.&lt;br /&gt;
===Install git===&lt;br /&gt;
You will need to install Git in order to get the source code and upload your changes. If you are not familiar with it, we recommend that you get started reading the following guide: [https://git-scm.com/book/en/v2/Getting-Started-Installing-Git Installing Git].&lt;br /&gt;
===Install Node.js and Npm===&lt;br /&gt;
We recommend using a version manager like [https://github.com/nvm-sh/nvm nvm] to make this easier, you can prepare the correct environment running &amp;lt;code&amp;gt;nvm install&amp;lt;/code&amp;gt; in the project root. Remember to run this every time you work with the app, or if you’re not working on any other node projects in your computer you can run &amp;lt;code&amp;gt;nvm alias default `node -v`&amp;lt;/code&amp;gt; to make it the default. &lt;br /&gt;
&lt;br /&gt;
It may seem simpler and easier to install directly from [http://nodejs.org nodejs.org], but actually it is more tricky to get that to work. If you have previously installed Node directly, and want to switch to nvm, you need to uninstall node completely before installing nvm - or search for trouble-shooting instructions online.&lt;br /&gt;
===Install native SDKs===&lt;br /&gt;
If you intend to run the application in a native device, you will need to install that platform&#039;s native SDKs. You can learn how to set up your environment by reading Ionic’s documentation for [https://ionicframework.com/docs/developing/android Android] and [https://ionicframework.com/docs/developing/ios iOS].&lt;br /&gt;
===Windows only: Native build dependencies===&lt;br /&gt;
&amp;lt;code&amp;gt;node-gyp&amp;lt;/code&amp;gt; requires native build tools for your platform. If you&#039;re developing on Mac or Linux, you&#039;ll probably have these already ([https://github.com/nodejs/node-gyp/blob/master/README.md refer to the docs if you don&#039;t]). On Windows, run the following command as administrator (in cmd or Powershell):&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
npm install --global --production windows-build-tools&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Warning! This installer can take a very, very long time to run. We were seeing it take hours. Literally. Be prepared to be very patient. Don&#039;t just make the natural assumption that it has crashed.&lt;br /&gt;
===Mac only: Push notifications===&lt;br /&gt;
This is only be necessary if you intend to compile the native iOS application. The push notifications plugin requires CocoaPods to work on a Mac; you can find the complete installation instructions in [https://cocoapods.org/ the official documentation], but it should work by running the following commands:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
sudo gem install cocoapods&lt;br /&gt;
pod setup&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Please note that for compiling the app in Mac you need to open the &#039;&#039;&#039;Moodle.xcworkspace&#039;&#039;&#039; file, more information here: MOBILE-1970.&lt;br /&gt;
===Linux only: libsecret===&lt;br /&gt;
If you are using [[Moodle App scripts: gulp push|the gulp push script]], you need to have &amp;lt;code&amp;gt;libsecret&amp;lt;/code&amp;gt; installed before running &amp;lt;code&amp;gt;npm install&amp;lt;/code&amp;gt;. Depending on your distribution, you will need to run one of the following commands:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Debian/Ubuntu&lt;br /&gt;
sudo apt-get install libsecret-1-dev&lt;br /&gt;
&lt;br /&gt;
# Red Hat&lt;br /&gt;
sudo yum install libsecret-devel&lt;br /&gt;
&lt;br /&gt;
# Arch Linux&lt;br /&gt;
sudo pacman -S libsecret&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== Running the app in a browser ==&lt;br /&gt;
You can obtain a copy of the source code by cloning the public repository. If you want to work on the latest development version, you should check out the &amp;lt;code&amp;gt;integration&amp;lt;/code&amp;gt; branch:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
git clone git@github.com:moodlehq/moodleapp.git&lt;br /&gt;
cd moodleapp&lt;br /&gt;
git checkout integration&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Once you have the correct environment set up, you can run the application with the following two commands:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
npm install&lt;br /&gt;
npm start&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This will launch the application in a browser and you should be ready to start coding (you may also want to [[Using the Moodle App in a browser#Configuring_the_default_browser|configure the default browser]] for future runs). This compiles the entire application and can take a while, so don&#039;t worry if it doesn&#039;t open the browser instantly. Keep in mind that this command may open the browser before the dev server is ready, and you could get a network error. If that happens, just wait until the dev server is ready and launch the application again. It should be ready when you see a &amp;quot;Compiled successfully&amp;quot; message in the console. &lt;br /&gt;
&lt;br /&gt;
Congratulations, you have just completed the basics to become a Moodle App developer!&lt;br /&gt;
&lt;br /&gt;
If you need to work with native features or build packaged versions of the app, you can read the rest of this page.&lt;br /&gt;
== Running the app in Android and iOS ==&lt;br /&gt;
The first time you want to run the application in a native device, this process will take a bit longer than usual because it needs to create the native projects and install native plugins. These will be created under the &amp;lt;code&amp;gt;platforms/&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;plugins/&amp;lt;/code&amp;gt; folders respectively. If you ever run into issues during this process, try deleting both of these folders to get a clean start.&lt;br /&gt;
&lt;br /&gt;
In order to run the application on a native device, you can use one of the following commands:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
npm run dev:android  # Uses Live Reload, read below&lt;br /&gt;
npm run dev:ios      # Does NOT use Live Reload, read below&lt;br /&gt;
npm run prod:android # Uses AOT compilation, read below&lt;br /&gt;
npm run prod:ios     # Uses AOT compilation, read below&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If you get any errors while building, please see the [[#Troubleshooting|Troubleshooting]] section below.&lt;br /&gt;
=== Developing using Live Reload ===&lt;br /&gt;
Most of the time, it is recommended to develop using the &amp;lt;code&amp;gt;npm start&amp;lt;/code&amp;gt; command. Working in a browser is faster, easier, and doesn&#039;t have as much overhead. However, in some situations you may want to run the application in a native device. If you want a similar development experience, you&#039;ll want to enable [https://ionicframework.com/docs/cli/livereload Live Reload].&lt;br /&gt;
&lt;br /&gt;
In Android, you can use the &amp;lt;code&amp;gt;npm run dev:android&amp;lt;/code&amp;gt; command and the application will launch on a device or emulator. Whenever you make any changes to your code, the application will reload automatically. Keep in mind that this command may launch the application before the dev server is ready, and you could get a network error. If that happens, just wait until the dev server is ready and launch the application again (you should see &amp;quot;Compiled successfully&amp;quot; in the console).&lt;br /&gt;
&lt;br /&gt;
In iOS, there are some limitations using live reload and that&#039;s why the &amp;lt;code&amp;gt;npm run dev:ios&amp;lt;/code&amp;gt; command doesn&#039;t enable it by default. You can still use it by running &amp;lt;code&amp;gt;npx ionic cordova run ios --livereload --external&amp;lt;/code&amp;gt;, but keep in mind that this will serve your application on an IP and will expose it to anyone connected to the same Wi-Fi network. Other than privacy concerns, this may cause some problems if you are working with iframes or local files. If you have any problems with that, you&#039;ll have to use the &amp;lt;code&amp;gt;npm run dev:ios&amp;lt;/code&amp;gt; command to recompile the application every time you change the code.&lt;br /&gt;
=== Compiling using AOT ===&lt;br /&gt;
Angular has 2 ways of compiling: [https://angular.io/guide/glossary#jit JIT] and [https://angular.io/guide/glossary#aot AOT].&lt;br /&gt;
&lt;br /&gt;
Running &amp;lt;code&amp;gt;npm start&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;npm run dev:android&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;npm run dev:ios&amp;lt;/code&amp;gt; compiles using JIT, which is faster to compile but the app takes longer to start. This is acceptable during development because it allows you to use Live Reload.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;npm run prod:android&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;npm run prod:ios&amp;lt;/code&amp;gt; commands use AOT compilation because they generate production bundles.&lt;br /&gt;
=== Using Android emulators ===&lt;br /&gt;
Most of the time, you should be using an emulator running recent versions of Android, and it should work fine. But sometimes, you may want to use an older version to test a specific behaviour.&lt;br /&gt;
&lt;br /&gt;
If you want to run the application in an Android 5 emulator, you’ll need to upgrade the system webview because emulators come with version 37 preinstalled. Your first idea may be to upgrade the webview using the Google Play store, but it will not work because the webview served by Google Play is &amp;lt;code&amp;gt;com.google.android.webview&amp;lt;/code&amp;gt; whilst the system webview used in emulators is &amp;lt;code&amp;gt;com.android.webview&amp;lt;/code&amp;gt;. You can do the following instead.&lt;br /&gt;
&lt;br /&gt;
Once you have [https://developer.android.com/studio/run/managing-avds created your Android 5 virtual device], you’ll need to do download [https://android.googlesource.com/platform/external/chromium-webview/+/refs/heads/oreo-m3-release/prebuilt/x86_64/ the apk for Webview 61] and run the following commands:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
# Open the folder where the “emulator” script is installed&lt;br /&gt;
cd $(dirname `which emulator`)&lt;br /&gt;
&lt;br /&gt;
# Boot the emulator in write mode&lt;br /&gt;
# (you can get a list of device names running “emulator -list-avds”)&lt;br /&gt;
emulator @DeviceName -writable-system&lt;br /&gt;
&lt;br /&gt;
# In a different shell, make /system writable&lt;br /&gt;
adb remount&lt;br /&gt;
&lt;br /&gt;
# Uninstall the webview app manually and reboot the device&lt;br /&gt;
adb shell&lt;br /&gt;
rm -rf /data/data/com.android.webview&lt;br /&gt;
rm -rf /system/app/webview&lt;br /&gt;
reboot&lt;br /&gt;
&lt;br /&gt;
# Install the new version&lt;br /&gt;
adb install webview.apk&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
After doing this, remember to run the emulator in write mode for subsequent sessions, but you don’t need to call the &amp;lt;code&amp;gt;remount&amp;lt;/code&amp;gt; command every time.&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
This section contains a list of common errors that have been found and how to solve them. However, keep in mind that these may be platform-dependent and could not fix the problem in your machine, even if you are seeing the same error message.&lt;br /&gt;
=== General advice ===&lt;br /&gt;
If you are stuck with an error and you can&#039;t find a way to continue, here&#039;s a list of things you can do:&lt;br /&gt;
* Using git, look at the changes you have in your working directory and make sure that they aren&#039;t causing the problem. Be specially careful with changes in &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;package-lock.json&amp;lt;/code&amp;gt;. You can see a list of the files you have modified running &amp;lt;code&amp;gt;git status&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Make sure that you are using the proper node and npm versions. You can see it looking at the &amp;lt;code&amp;gt;engines&amp;lt;/code&amp;gt; key in &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt;. If you are using [https://github.com/nvm-sh/nvm nvm], just run &amp;lt;code&amp;gt;nvm install&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Make sure that all dependencies have been installed properly. To be extra sure, run &amp;lt;code&amp;gt;npm ci&amp;lt;/code&amp;gt;; this will remove the &amp;lt;code&amp;gt;node_modules/&amp;lt;/code&amp;gt; folder and install all dependencies again exactly as described in your &amp;lt;code&amp;gt;package-lock.json&amp;lt;/code&amp;gt;.&lt;br /&gt;
* If you are having issues trying to build for Android or iOS, try removing the &amp;lt;code&amp;gt;www/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;platforms/&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;plugins/&amp;lt;/code&amp;gt; folders and try again.&lt;br /&gt;
* If you are using a development version, maybe the repository is broken and it&#039;s not your fault. Try checking out the &amp;lt;code&amp;gt;master&amp;lt;/code&amp;gt; branch and see if you&#039;re getting the same error.&lt;br /&gt;
* Try cloning the repository in a new folder and run through the instructions in this page again. If you can, try doing it on a different computer to make sure that you&#039;re doing everything properly and it&#039;s not a problem in your machine.&lt;br /&gt;
* Try creating [https://ionicframework.com/docs/cli/commands/start a blank Ionic application] and see if you&#039;re having the same problems. Make sure that you are using the same version of the main dependencies (Angular, Cordova, Ionic CLI, etc.).&lt;br /&gt;
* If you are searching for help online, maybe your problem has nothing to do with the Moodle App in particular an it&#039;s related with Ionic, Cordova, Angular, etc. Searching using the proper context will give you better solutions.&lt;br /&gt;
=== I get a blank page when launching the app ===&lt;br /&gt;
This error can happen when the application is launched properly but there is a runtime error. If you get a network connection error, this may be the expected behaviour. Wait until you see &amp;quot;Compiled successfully&amp;quot; in the console and reload.&lt;br /&gt;
&lt;br /&gt;
If that&#039;s not the case and you really are getting a blank page, look at the console and you should see some error indicating why the application is not rendering properly. Keep in mind that at the moment the application is already logging some errors and warnings, so make sure that you&#039;re not stuck trying to fix something that isn&#039;t causing the problem. You can follow any updates about this on MOBILE-3854.&lt;br /&gt;
&lt;br /&gt;
If you are not seeing any relevant logs, check out what to do on the [[#General_advice|General advice]] section.&lt;br /&gt;
=== Strange NPM errors ===&lt;br /&gt;
To get more debug output from npm commands, see [https://docs.npmjs.com/cli/v7/using-npm/config the available configuration flags]. In particular try adding &amp;lt;code&amp;gt;--loglevel verbose&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;--loglevel info&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;--loglevel silly&amp;lt;/code&amp;gt; to the command-line.&lt;br /&gt;
=== I can&#039;t change the language ===&lt;br /&gt;
If you&#039;re getting a network error for a url like &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;http://localhost:8100/assets/lang/es.json&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;, this probably means that you haven&#039;t installed the language packs.&lt;br /&gt;
&lt;br /&gt;
Currently, you can install them in your machine running the &amp;lt;code&amp;gt;scripts/update_lang.sh&amp;lt;/code&amp;gt; script, but it may not work in your system if you don&#039;t have php installed. For future improvements, you can subscribe to MOBILE-3864.&lt;br /&gt;
=== Error: libsass bindings not found. Try reinstalling node-sass? ===&lt;br /&gt;
Most of the time, running the following command will fix the problem:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
npm rebuild node-sass&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== com.android.dex.DexException: Multiple dex files define XXX ===&lt;br /&gt;
Open the file &amp;lt;code&amp;gt;platforms/android/build.gradle&amp;lt;/code&amp;gt; and add this code at the end:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;groovy&amp;quot;&amp;gt;&lt;br /&gt;
configurations {&lt;br /&gt;
    all*.exclude group: &#039;com.android.support&#039;, module: &#039;support-v4&#039;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Could not resolve all dependencies for configuration &#039;:_debugCompile&#039;. ===&lt;br /&gt;
Open the Android SDK Manager and make sure you have installed: Android Support Repository, Android Support Library, Google Play Services and Google Repository.&lt;br /&gt;
=== Could not find com.android.support:support-v4:XXX ===&lt;br /&gt;
Open the file &amp;lt;code&amp;gt;platforms/android/build.gradle&amp;lt;/code&amp;gt; and add this code at the end:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;groovy&amp;quot;&amp;gt;&lt;br /&gt;
configurations.all {&lt;br /&gt;
    resolutionStrategy.force &#039;com.android.support:support-v4:24.0.0&#039;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== ERROR: In &amp;lt;declare-styleable&amp;gt; FontFamilyFont, unable to find attribute android:font ===&lt;br /&gt;
Open the file &amp;lt;code&amp;gt;platforms/android/build.gradle&amp;lt;/code&amp;gt; and add this code at the end:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;groovy&amp;quot;&amp;gt;&lt;br /&gt;
android {&lt;br /&gt;
   compileSdkVersion 26&lt;br /&gt;
   buildToolsVersion &amp;quot;26.0.1&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Error: Could not find gradle wrapper within Android SDK. Might need to update your Android SDK. ===&lt;br /&gt;
Download [https://developer.android.com/studio/ Android Studio] and copy the folder &amp;lt;code&amp;gt;android-studio/plugins/android/lib/templates&amp;lt;/code&amp;gt; into &amp;lt;code&amp;gt;android-sdk-folder/Sdk/tools&amp;lt;/code&amp;gt;.&lt;br /&gt;
=== Could not find com.android.support:support-v4:27.1.0 ===&lt;br /&gt;
Open the file &amp;lt;code&amp;gt;platforms/android/build.gradle&amp;lt;/code&amp;gt; and configure like this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;groovy&amp;quot;&amp;gt;&lt;br /&gt;
allprojects {&lt;br /&gt;
    repositories {&lt;br /&gt;
        jcenter()&lt;br /&gt;
        maven {&lt;br /&gt;
            url &amp;quot;https://maven.google.com&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Error: not found: make ===&lt;br /&gt;
If you see this error in Ubuntu, run &amp;lt;code&amp;gt;sudo apt-get install build-essential&amp;lt;/code&amp;gt; and retry.&lt;br /&gt;
=== Current working directory is not a Cordova-based project. ===&lt;br /&gt;
If you see this error during &amp;lt;code&amp;gt;npm start&amp;lt;/code&amp;gt;, run &amp;lt;code&amp;gt;mkdir www&amp;lt;/code&amp;gt; and retry.&lt;br /&gt;
=== ReferenceError: internalBinding is not defined ===&lt;br /&gt;
This [https://stackoverflow.com/questions/53146394/node-app-fails-to-run-on-mojave-referenceerror-internalbinding-is-not-defined seems to be] an error with &amp;lt;code&amp;gt;natives&amp;lt;/code&amp;gt; prior to 1.1.6. It can be fixed by running &amp;lt;code&amp;gt;npm install natives@1.1.6&amp;lt;/code&amp;gt;.&lt;br /&gt;
=== npm update check failed ===&lt;br /&gt;
You may get the following error on Windows:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
 │                   npm update check failed                   │&lt;br /&gt;
 │             Try running with sudo or get access             │ &lt;br /&gt;
 │            to the local update config store via             │&lt;br /&gt;
 │ sudo chown -R $USER:$(id -gn $USER) C:\Users\username\.config │&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The suggested command does not work on Windows, so the solution is to manually check the ownership of all the files in &amp;lt;code&amp;gt;C:\Users\{username}\.config\configstore&amp;lt;/code&amp;gt;. In some cases, it can be &amp;lt;code&amp;gt;update-notifier-npm.json&amp;lt;/code&amp;gt; that got changed to be owned by Administrator.&lt;br /&gt;
=== Unhandled rejection Error: Command failed: C:\cygwin64\bin\git.EXE ... ===&lt;br /&gt;
This is a common issue for Cygwin user running Node. However, you just need to ensure that Msysgit is on your windows path and that the cygwin bin folder is not. Then always use another shell like Powershell for your Moodle App development.&lt;br /&gt;
&lt;br /&gt;
You don&#039;t need your Cygwin bin folder on the Windows path because it automatically gets added to the path when you lauch Cygwin bash.&lt;br /&gt;
=== The product name change (&amp;lt;name&amp;gt; tag) in config.xml is not supported dynamically ===&lt;br /&gt;
This happens when you create the iOS platform with a certain &amp;lt;name&amp;gt; and then you change that name in config.xml. The solution seems to be removing and adding the iOS platform again:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
npx ionic platform remove ios&lt;br /&gt;
npx ionic platform add ios&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
===Failed to install &#039;cordova-plugin-x&#039; ===&lt;br /&gt;
Sometimes, you may see an error message similar to this:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
CordovaError: Version of installed plugin: &amp;quot;cordova-plugin-x@x.x.x&amp;quot; does not satisfy dependency plugin requirement &amp;quot;cordova-plugin-x@&amp;gt;=x.x.x&amp;quot;.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This can happen when a cordova plugin (let&#039;s call it &amp;quot;X&amp;quot;) is installed with an incorrect version. You can find this by removing the plugin and adding it again with the correct version:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
npx cordova plugin remove cordova-plugin-x&lt;br /&gt;
npx cordova plugin add cordova-plugin-x@x.x.x # Make sure to use the proper version here&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Please notice that if there is any plugin installed that depends on &amp;lt;code&amp;gt;cordova-plugin-x&amp;lt;/code&amp;gt; you&#039;ll have to remove and re-add them too.&lt;br /&gt;
=== doc.find is not a function ===&lt;br /&gt;
This happens in some environments, the solution is to run &amp;lt;code&amp;gt;npx cordova platform add ios&amp;lt;/code&amp;gt; before running the failing command. You should do this in a clean environment, once you&#039;ve seen the error running the command may not work. Try deleting &amp;lt;code&amp;gt;www/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;platforms/&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;plugins/&amp;lt;/code&amp;gt; before trying again.&lt;br /&gt;
&lt;br /&gt;
[https://stackoverflow.com/questions/47404622/edit-config-for-ios-usage-descriptions-doc-find-is-not-a-function Find more about this in StackOverflow]&lt;br /&gt;
===Mac: linker code failed with exit code 1===&lt;br /&gt;
If you get this error when trying to build the Moodle app with XCode, some dependencies might not have installed correctly.&lt;br /&gt;
&lt;br /&gt;
Ensure you have followed the [#Mac_only:_Push_notifications Mac only: Push notifications] steps above (particularly opening the .xcworkspace file rather than the .xcodeproj file). Then run the following:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
cd platforms/ios&lt;br /&gt;
pod install&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now try running the build again in XCode.&lt;br /&gt;
===Windows: &amp;lt;code&amp;gt;npm start&amp;lt;/code&amp;gt; hangs after &amp;quot;Starting &#039;watch&#039;&amp;quot;===&lt;br /&gt;
Try running the commands generated by &amp;lt;code&amp;gt;npm start&amp;lt;/code&amp;gt; directly in bash:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;bash&amp;quot;&amp;gt;&lt;br /&gt;
npx gulp watch &amp;amp; NODE_OPTIONS=--max-old-space-size=4096 npx ng serve &amp;quot;--host=0.0.0.0&amp;quot; &amp;quot;--port=8100&amp;quot; &amp;quot;--project=app&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Moodle App Coding Style]]&lt;br /&gt;
* [[Debugging network requests in the Moodle_App]]&lt;br /&gt;
* [https://github.com/moodlehq/moodle-docker Moodle Docker]&lt;br /&gt;
* [http://ionicframework.com/docs/cli/ Ionic CLI docs]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=58195</id>
		<title>Creating mobile course formats</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=58195"/>
		<updated>2021-01-08T09:33:39Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Add page to the Moodle Mobile category&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile}}&lt;br /&gt;
&lt;br /&gt;
Course format plugins can be supported in the app using the CoureCourseFormatDelegate.&lt;br /&gt;
&lt;br /&gt;
To begin, add a handler for this delegate to your course format&#039;s db/mobile.php file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;format_myformat&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;myformat&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseFormatDelegate&#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 \format_myformat\output\mobile.&lt;br /&gt;
                &#039;styles&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;url&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/course/format/myformat/mobile.css&#039;,&lt;br /&gt;
                    &#039;version&#039; =&amp;gt; 2019041000&lt;br /&gt;
                ],&lt;br /&gt;
                &#039;displaysectionselector&#039; =&amp;gt; true, // Set to false to disable the default section selector.&lt;br /&gt;
                &#039;displayenabledownload&#039; =&amp;gt; true, // Set to false to hide the &amp;quot;Enable download&amp;quot; option in the course context menu.&lt;br /&gt;
                &#039;init&#039; =&amp;gt; &#039;myformat_init&#039;&lt;br /&gt;
            ]&lt;br /&gt;
    ]&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As with other plugins, you then use a function in your plugin&#039;s classes/output/mobile.php to return a template:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Main course page.&lt;br /&gt;
     *&lt;br /&gt;
     * @param array $args Standard mobile web service arguments&lt;br /&gt;
     * @return array&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $CFG;&lt;br /&gt;
&lt;br /&gt;
        $course = get_course($args[&#039;courseid&#039;]);&lt;br /&gt;
        require_login($course);&lt;br /&gt;
        $html = $OUTPUT-&amp;gt;render_from_template(&#039;format_myformat/mobile_course&#039;, []);&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; $html&lt;br /&gt;
                ]&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
               ...&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then your templates/mobile_course.mustache file will contain the angular template to display your page&lt;br /&gt;
&amp;lt;code html&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;allSectionsComponent&amp;quot; [data]=&amp;quot;data&amp;quot; class=&amp;quot;format-myformat&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let section of sections&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-item-divider color=&amp;quot;light&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;core-format-text [text]=&amp;quot;section.name&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
             &amp;lt;!-- Section download. --&amp;gt;&lt;br /&gt;
             &amp;lt;div *ngIf=&amp;quot;section &amp;amp;&amp;amp; downloadEnabled&amp;quot; class=&amp;quot;core-button-spinner&amp;quot; float-end&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Download button. --&amp;gt;&lt;br /&gt;
                &amp;lt;button *ngIf=&amp;quot;section.showDownload &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.download&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                &amp;lt;/button&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Refresh button. --&amp;gt;&lt;br /&gt;
                &amp;lt;button *ngIf=&amp;quot;section.showRefresh &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.refresh&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-icon name=&amp;quot;refresh&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                &amp;lt;/button&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Download progress. --&amp;gt;&lt;br /&gt;
                &amp;lt;ion-badge class=&amp;quot;core-course-download-section-progress&amp;quot; *ngIf=&amp;quot;section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0 &amp;amp;&amp;amp; section.count &amp;lt; section.total&amp;quot;&amp;gt;{{section.count}} / {{section.total}}&amp;lt;/ion-badge&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Spinner (downloading or calculating status). --&amp;gt;&lt;br /&gt;
                &amp;lt;ion-spinner *ngIf=&amp;quot;(section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0) || section.isCalculating&amp;quot;&amp;gt;&amp;lt;/ion-spinner&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-item-divider&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;ion-item text-wrap *ngIf=&amp;quot;section.summary&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;core-format-text [text]=&amp;quot;section.summary&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;ng-container *ngFor=&amp;quot;let module of section.modules&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;ng-container *ngIf=&amp;quot;module.visibleoncoursepage !== 0&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;core-course-module text-wrap [module]=&amp;quot;module&amp;quot; [courseId]=&amp;quot;course.id&amp;quot; [downloadEnabled]=&amp;quot;downloadEnabled&amp;quot; (completionChanged)=&amp;quot;onCompletionChange($event)&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;/core-course-module&amp;gt;&lt;br /&gt;
            &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
        &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
    &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
You don&#039;t have to use a mustache template, you can just use a static angular template.&lt;br /&gt;
&lt;br /&gt;
This will get you to a stage where you have something similar to the core format plugin - a list of sections with headers, each containing its list of course modules.  From here, you can make customisations to support your course format&#039;s features.&lt;br /&gt;
&lt;br /&gt;
Note that there are a few objects available to your template without you having to do anything:&lt;br /&gt;
* sections - this is an array of all the sections on the course, which includes all of the modules within that course.&lt;br /&gt;
* course - this contains the basic course data&lt;br /&gt;
* downloadEnabled - This is set using the option int he context menu, if &amp;quot;displayenabledownload&amp;quot; is used in your db.php&lt;br /&gt;
&lt;br /&gt;
== Example: only display certain sections ==&lt;br /&gt;
When your course page loads, the sections array contains all of the sections on the course.  However, you might not want to display all of these sections on one page.&lt;br /&gt;
You can achieve this by returning the list of sections to display along with the template in classes/output/mobile.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class mobile {&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
       ...&lt;br /&gt;
       $displaysections = myformat\helper::get_list_of_section_ids($courseid);&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; $html&lt;br /&gt;
                ]&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
               &#039;displaysections&#039; =&amp;gt; json_encode($displaysections);&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then filter the list of sections in your template:&lt;br /&gt;
&amp;lt;code html&amp;gt;&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let section of sections&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ng-container *ngIf=&amp;quot;CONTENT_OTHERDATA.displaysections.hasOwnProperty(section.id.toString())&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- code to display the section goes here --&amp;gt;&lt;br /&gt;
        &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
    &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Mobile]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=58194</id>
		<title>Creating mobile course formats</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=58194"/>
		<updated>2021-01-08T09:26:07Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Course format plugins can be supported in the app using the CoureCourseFormatDelegate.&lt;br /&gt;
&lt;br /&gt;
To begin, add a handler for this delegate to your course format&#039;s db/mobile.php file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;format_myformat&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;myformat&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseFormatDelegate&#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 \format_myformat\output\mobile.&lt;br /&gt;
                &#039;styles&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;url&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/course/format/myformat/mobile.css&#039;,&lt;br /&gt;
                    &#039;version&#039; =&amp;gt; 2019041000&lt;br /&gt;
                ],&lt;br /&gt;
                &#039;displaysectionselector&#039; =&amp;gt; true, // Set to false to disable the default section selector.&lt;br /&gt;
                &#039;displayenabledownload&#039; =&amp;gt; true, // Set to false to hide the &amp;quot;Enable download&amp;quot; option in the course context menu.&lt;br /&gt;
                &#039;init&#039; =&amp;gt; &#039;myformat_init&#039;&lt;br /&gt;
            ]&lt;br /&gt;
    ]&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As with other plugins, you then use a function in your plugin&#039;s classes/output/mobile.php to return a template:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Main course page.&lt;br /&gt;
     *&lt;br /&gt;
     * @param array $args Standard mobile web service arguments&lt;br /&gt;
     * @return array&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $CFG;&lt;br /&gt;
&lt;br /&gt;
        $course = get_course($args[&#039;courseid&#039;]);&lt;br /&gt;
        require_login($course);&lt;br /&gt;
        $html = $OUTPUT-&amp;gt;render_from_template(&#039;format_myformat/mobile_course&#039;, []);&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; $html&lt;br /&gt;
                ]&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
               ...&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then your templates/mobile_course.mustache file will contain the angular template to display your page&lt;br /&gt;
&amp;lt;code html&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;allSectionsComponent&amp;quot; [data]=&amp;quot;data&amp;quot; class=&amp;quot;format-myformat&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let section of sections&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-item-divider color=&amp;quot;light&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;core-format-text [text]=&amp;quot;section.name&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
             &amp;lt;!-- Section download. --&amp;gt;&lt;br /&gt;
             &amp;lt;div *ngIf=&amp;quot;section &amp;amp;&amp;amp; downloadEnabled&amp;quot; class=&amp;quot;core-button-spinner&amp;quot; float-end&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Download button. --&amp;gt;&lt;br /&gt;
                &amp;lt;button *ngIf=&amp;quot;section.showDownload &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.download&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                &amp;lt;/button&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Refresh button. --&amp;gt;&lt;br /&gt;
                &amp;lt;button *ngIf=&amp;quot;section.showRefresh &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.refresh&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-icon name=&amp;quot;refresh&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                &amp;lt;/button&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Download progress. --&amp;gt;&lt;br /&gt;
                &amp;lt;ion-badge class=&amp;quot;core-course-download-section-progress&amp;quot; *ngIf=&amp;quot;section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0 &amp;amp;&amp;amp; section.count &amp;lt; section.total&amp;quot;&amp;gt;{{section.count}} / {{section.total}}&amp;lt;/ion-badge&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Spinner (downloading or calculating status). --&amp;gt;&lt;br /&gt;
                &amp;lt;ion-spinner *ngIf=&amp;quot;(section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0) || section.isCalculating&amp;quot;&amp;gt;&amp;lt;/ion-spinner&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-item-divider&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;ion-item text-wrap *ngIf=&amp;quot;section.summary&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;core-format-text [text]=&amp;quot;section.summary&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;ng-container *ngFor=&amp;quot;let module of section.modules&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;ng-container *ngIf=&amp;quot;module.visibleoncoursepage !== 0&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;core-course-module text-wrap [module]=&amp;quot;module&amp;quot; [courseId]=&amp;quot;course.id&amp;quot; [downloadEnabled]=&amp;quot;downloadEnabled&amp;quot; (completionChanged)=&amp;quot;onCompletionChange($event)&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;/core-course-module&amp;gt;&lt;br /&gt;
            &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
        &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
    &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
You don&#039;t have to use a mustache template, you can just use a static angular template.&lt;br /&gt;
&lt;br /&gt;
This will get you to a stage where you have something similar to the core format plugin - a list of sections with headers, each containing its list of course modules.  From here, you can make customisations to support your course format&#039;s features.&lt;br /&gt;
&lt;br /&gt;
Note that there are a few objects available to your template without you having to do anything:&lt;br /&gt;
* sections - this is an array of all the sections on the course, which includes all of the modules within that course.&lt;br /&gt;
* course - this contains the basic course data&lt;br /&gt;
* downloadEnabled - This is set using the option int he context menu, if &amp;quot;displayenabledownload&amp;quot; is used in your db.php&lt;br /&gt;
&lt;br /&gt;
== Example: only display certain sections ==&lt;br /&gt;
When your course page loads, the sections array contains all of the sections on the course.  However, you might not want to display all of these sections on one page.&lt;br /&gt;
You can achieve this by returning the list of sections to display along with the template in classes/output/mobile.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class mobile {&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
       ...&lt;br /&gt;
       $displaysections = myformat\helper::get_list_of_section_ids($courseid);&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; $html&lt;br /&gt;
                ]&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
               &#039;displaysections&#039; =&amp;gt; json_encode($displaysections);&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then filter the list of sections in your template:&lt;br /&gt;
&amp;lt;code html&amp;gt;&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let section of sections&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ng-container *ngIf=&amp;quot;CONTENT_OTHERDATA.displaysections.hasOwnProperty(section.id.toString())&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- code to display the section goes here --&amp;gt;&lt;br /&gt;
        &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
    &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=58193</id>
		<title>Creating mobile course formats</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=58193"/>
		<updated>2021-01-08T09:25:16Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Add missing render_from_template parameter&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Course format plugins can be supported in the app using the CoureCourseFormatDelegate.&lt;br /&gt;
&lt;br /&gt;
To begin, add a handler for this delegate to your course format&#039;s db/mobile.php file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;format_myformat&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;myformat&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseFormatDelegate&#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 \format_myformat\output\mobile.&lt;br /&gt;
                &#039;styles&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;url&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/course/format/myformat/mobile.css&#039;,&lt;br /&gt;
                    &#039;version&#039; =&amp;gt; 2019041000&lt;br /&gt;
                ],&lt;br /&gt;
                &#039;displaysectionselector&#039; =&amp;gt; true, // Set to false to disable the default section selector.&lt;br /&gt;
                &#039;displayenabledownload&#039; =&amp;gt; true, // Set to false to hide the &amp;quot;Enable download&amp;quot; option in the course context menu.&lt;br /&gt;
                &#039;init&#039; =&amp;gt; &#039;myformat_init&#039;&lt;br /&gt;
            ]&lt;br /&gt;
    ]&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As with other plugins, you then use a function in your plugin&#039;s classes/output/mobile.php to return a template:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Main course page.&lt;br /&gt;
     *&lt;br /&gt;
     * @param array $args Standard mobile web service arguments&lt;br /&gt;
     * @return array&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $CFG;&lt;br /&gt;
&lt;br /&gt;
        $course = get_course($args[&#039;courseid&#039;]);&lt;br /&gt;
        require_login($course);&lt;br /&gt;
        $html = $OUTPUT-&amp;gt;render_from_template(&#039;format_oustudyplan/mobile_course&#039;, []);&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; $html&lt;br /&gt;
                ]&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
               ...&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then your templates/mobile_course.mustache file will contain the angular template to display your page&lt;br /&gt;
&amp;lt;code html&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;allSectionsComponent&amp;quot; [data]=&amp;quot;data&amp;quot; class=&amp;quot;format-myformat&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let section of sections&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-item-divider color=&amp;quot;light&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;core-format-text [text]=&amp;quot;section.name&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
             &amp;lt;!-- Section download. --&amp;gt;&lt;br /&gt;
             &amp;lt;div *ngIf=&amp;quot;section &amp;amp;&amp;amp; downloadEnabled&amp;quot; class=&amp;quot;core-button-spinner&amp;quot; float-end&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Download button. --&amp;gt;&lt;br /&gt;
                &amp;lt;button *ngIf=&amp;quot;section.showDownload &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.download&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                &amp;lt;/button&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Refresh button. --&amp;gt;&lt;br /&gt;
                &amp;lt;button *ngIf=&amp;quot;section.showRefresh &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.refresh&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-icon name=&amp;quot;refresh&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                &amp;lt;/button&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Download progress. --&amp;gt;&lt;br /&gt;
                &amp;lt;ion-badge class=&amp;quot;core-course-download-section-progress&amp;quot; *ngIf=&amp;quot;section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0 &amp;amp;&amp;amp; section.count &amp;lt; section.total&amp;quot;&amp;gt;{{section.count}} / {{section.total}}&amp;lt;/ion-badge&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Spinner (downloading or calculating status). --&amp;gt;&lt;br /&gt;
                &amp;lt;ion-spinner *ngIf=&amp;quot;(section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0) || section.isCalculating&amp;quot;&amp;gt;&amp;lt;/ion-spinner&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-item-divider&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;ion-item text-wrap *ngIf=&amp;quot;section.summary&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;core-format-text [text]=&amp;quot;section.summary&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;ng-container *ngFor=&amp;quot;let module of section.modules&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;ng-container *ngIf=&amp;quot;module.visibleoncoursepage !== 0&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;core-course-module text-wrap [module]=&amp;quot;module&amp;quot; [courseId]=&amp;quot;course.id&amp;quot; [downloadEnabled]=&amp;quot;downloadEnabled&amp;quot; (completionChanged)=&amp;quot;onCompletionChange($event)&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;/core-course-module&amp;gt;&lt;br /&gt;
            &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
        &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
    &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
You don&#039;t have to use a mustache template, you can just use a static angular template.&lt;br /&gt;
&lt;br /&gt;
This will get you to a stage where you have something similar to the core format plugin - a list of sections with headers, each containing its list of course modules.  From here, you can make customisations to support your course format&#039;s features.&lt;br /&gt;
&lt;br /&gt;
Note that there are a few objects available to your template without you having to do anything:&lt;br /&gt;
* sections - this is an array of all the sections on the course, which includes all of the modules within that course.&lt;br /&gt;
* course - this contains the basic course data&lt;br /&gt;
* downloadEnabled - This is set using the option int he context menu, if &amp;quot;displayenabledownload&amp;quot; is used in your db.php&lt;br /&gt;
&lt;br /&gt;
== Example: only display certain sections ==&lt;br /&gt;
When your course page loads, the sections array contains all of the sections on the course.  However, you might not want to display all of these sections on one page.&lt;br /&gt;
You can achieve this by returning the list of sections to display along with the template in classes/output/mobile.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class mobile {&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
       ...&lt;br /&gt;
       $displaysections = myformat\helper::get_list_of_section_ids($courseid);&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; $html&lt;br /&gt;
                ]&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
               &#039;displaysections&#039; =&amp;gt; json_encode($displaysections);&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then filter the list of sections in your template:&lt;br /&gt;
&amp;lt;code html&amp;gt;&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let section of sections&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ng-container *ngIf=&amp;quot;CONTENT_OTHERDATA.displaysections.hasOwnProperty(section.id.toString())&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- code to display the section goes here --&amp;gt;&lt;br /&gt;
        &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
    &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57564</id>
		<title>Writing acceptance tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57564"/>
		<updated>2020-06-03T13:55:16Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Correct description of region selector and add list_item&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This documentation gives some hints, how to write behat tests for core and for plugins. The focus of the documentation is on behat tests for plugins. They are written in some kind of natural language and describe how the front-end of moodle should behave, when a user interacts with it.&lt;br /&gt;
&lt;br /&gt;
Each test consists of a set of so called steps in a GIVEN, WHEN, THEN style:&lt;br /&gt;
* &#039;&#039;&#039;GIVEN&#039;&#039;&#039;: These steps outline the state of your moodle platform at the start of your test. Here you can create users, courses and plugin instances.&lt;br /&gt;
* &#039;&#039;&#039;WHEN&#039;&#039;&#039;: These steps usually execute the functionality of your plugin, which is under test.&lt;br /&gt;
* &#039;&#039;&#039;THEN&#039;&#039;&#039;: These steps describe the expected behaviour of your plugin. They usually check if different elements can or can&#039;t be seen.&lt;br /&gt;
This matches the standard [http://xunitpatterns.com/Four%20Phase%20Test.html Four-phase test pattern]. The fourth phase is &#039;tear-down&#039; which we don&#039;t need because Behat automatically cleans up after each test.&lt;br /&gt;
&lt;br /&gt;
To initialize and run your tests, please follow the instructions of [[Running_acceptance_test]].&lt;br /&gt;
&lt;br /&gt;
== Create your own tests ==&lt;br /&gt;
Behat tests are located within the directory tests/behat of your plugin.&lt;br /&gt;
The different tests are defined in files with the ending *.feature.&lt;br /&gt;
First, you have to define the header of your test:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@mod @mod_yourplugin @javascript&lt;br /&gt;
Feature: Here comes a description of your user story.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The tags on top of the feature description can be used to select specific test cases when running the tests.&lt;br /&gt;
The &#039;@javascript&#039; tag should only be used, if javascript is needed to execute your test. This is dependent on the step you will use in your definition.&lt;br /&gt;
Javascript tests are usually much slower than tests executed without javascript.&lt;br /&gt;
&lt;br /&gt;
Afterwards you can specify a scenario:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@javascript&lt;br /&gt;
  Scenario: Description of your scenario, which you want to test.&lt;br /&gt;
    When I log in as &amp;quot;student1&amp;quot;&lt;br /&gt;
    And I am on &amp;quot;Course 1&amp;quot; course homepage&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Again you can define specific tags. Afterwards you write the steps, which should be executed during your test.&lt;br /&gt;
&lt;br /&gt;
==== Multiple Scenarios ====&lt;br /&gt;
You can have an arbitrary amount of scenarios within a test. Please make sure they all belong to the same feature.&lt;br /&gt;
If you have certain steps, which should be executed for every scenario of a feature, you can define them using a background:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
Background:&lt;br /&gt;
    Given the following &amp;quot;courses&amp;quot; exist:&lt;br /&gt;
      | fullname | shortname | category | groupmode |&lt;br /&gt;
      | Course 1 | C1        | 0        | 1         |&lt;br /&gt;
    And the following &amp;quot;users&amp;quot; exist:&lt;br /&gt;
      | username | firstname | lastname | email |&lt;br /&gt;
      | teacher1 | Theo | Teacher | teacher1@example.com |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This is usually used, to define the different &#039;&#039;&#039;GIVEN&#039;&#039;&#039; steps.&lt;br /&gt;
&lt;br /&gt;
==== Use existing steps ====&lt;br /&gt;
There are different ways how to effectively browse the available existing steps:&lt;br /&gt;
&lt;br /&gt;
====== Moodle Administration ======&lt;br /&gt;
Moodle offers within its administration menu under Site Administration &amp;gt; Development &amp;gt; Acceptance Testing a complete and searchable list of all available step definitions.&lt;br /&gt;
However, make sure you installed the behat test site first!&lt;br /&gt;
&lt;br /&gt;
====== PhpStorm ======&lt;br /&gt;
In PhpStorm or IntelliJ you can install the behat extension. Then you get auto completions within feature files, which helps a lot during behat test development.&lt;br /&gt;
&lt;br /&gt;
==== Providing values to steps ====&lt;br /&gt;
Most of the steps requires values, there are methods to provide values to steps, the method depends on the step specification.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;A string/text&#039;&#039;&#039;; is the most common case, the texts are wrapped between double quotes (&amp;quot; character) you have to replace the info about the expected value for your value; for example something like &#039;&#039;&#039;I press &amp;quot;BUTTON_STRING&amp;quot;&#039;&#039;&#039; should become &#039;&#039;&#039;I press &amp;quot;Save and return to course&amp;quot;&#039;&#039;&#039;. If you want to add a string which contains a &amp;quot; character, you can escape it with \&amp;quot;, for example &#039;&#039;&#039;I fill the &amp;quot;Name&amp;quot; field with &amp;quot;Alan alias \&amp;quot;the legend\&amp;quot;&amp;quot;&#039;&#039;&#039;. You can identify this steps because they ends with &#039;&#039;&#039;_STRING&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A number&#039;&#039;&#039;; some steps requires numbers as values, to be more specific an undetermined number of digits from 0 to 9 (Natural numbers + 0) you can identify them because the expected value info string ends with &#039;&#039;&#039;_NUMBER&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A table&#039;&#039;&#039;; is a relation between values, the most common use of it is to fill forms. The steps which requires tables are easily identifiable because they finish with &#039;&#039;&#039;:&#039;&#039;&#039; The steps description gives info about what the table columns must contain, for example &#039;&#039;&#039;Fills a moodle form with field/value data&#039;&#039;&#039;. Here you don&#039;t need to escape the double quotes if you want to include them as part of the value.&lt;br /&gt;
* &#039;&#039;&#039;A PyString&#039;&#039;&#039;; is a multiline string, most commonly used to fill out forms when a newline is required. Like steps with tables, steps which require PyStrings will end with &amp;quot;:&amp;quot;&lt;br /&gt;
* &#039;&#039;&#039;A field value&#039;&#039;&#039;; There are many different field types, if an argument requires a field value the expected value will depend on the field type:&lt;br /&gt;
** Text-based fields: It expects the text. This includes textareas, input type text, input type password...&lt;br /&gt;
** Checkbox: It expects 1 to check and for checked and &amp;quot;&amp;quot; to uncheck or for unchecked&lt;br /&gt;
** Select: It expects the option text or the option value. In case you interact with a multi-select you should specify the options separating them with commas. For example: &#039;&#039;&#039;option1, option2, option3&#039;&#039;&#039;&lt;br /&gt;
** Radio: The text of the radio option&lt;br /&gt;
* &#039;&#039;&#039;A selector&#039;&#039;&#039;; there are steps that can be used with different kinds of elements, for example &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;link&amp;quot;&#039;&#039;&#039; or &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;button&amp;quot;&#039;&#039;&#039; this is a closed list of elements, they always works together with another argument, where you specify the locator (eg. the link text in a link) In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** field - for searching a field by its id, name, value or label&lt;br /&gt;
** link - for searching a link by its href, id, title, img alt or value&lt;br /&gt;
** button - for searching a button by its name, id, value, img alt or title&lt;br /&gt;
** link_or_button - for searching for both, links and buttons&lt;br /&gt;
** select - for searching a select field by its id, name or label&lt;br /&gt;
** checkbox - for searching a checkbox by its id, name, or label&lt;br /&gt;
** radio - for searching a radio button by its id, name, or label&lt;br /&gt;
** file - for searching a file input by its id, name, or label&lt;br /&gt;
** optgroup - for searching optgroup by its label&lt;br /&gt;
** option - for searching an option by its content&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** filemanager - for searching a filemanager by it&#039;s id or label&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** icon - for searching an icon by its title &lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
* &#039;&#039;&#039;A text selector&#039;&#039;&#039;; similar to a selector but those are the elements that returns an area of the DOM, they are useful in steps following the format &#039;&#039;&#039;... in the &amp;quot;Community finder&amp;quot; &amp;quot;block&amp;quot;&#039;&#039;&#039; where you are clicking or looking for some text inside a specific area. In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all ids for &amp;lt;tt&amp;gt;div&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;section&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;aside&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;header&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;footer&amp;lt;/tt&amp;gt; elements.&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** list_item - for searching a list item which contains the specified text&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
&lt;br /&gt;
====== Checking table values ======&lt;br /&gt;
You can check if specific value exists or not in a table row/column by using:&lt;br /&gt;
* Then &amp;quot;STRING_IN_ROW&amp;quot; row &amp;quot;COLUMN_HEADER&amp;quot; column of &amp;quot;TABLE_ID&amp;quot; table should contain &amp;quot;VALUE_TO_CHECK&amp;quot;&lt;br /&gt;
* Then the following should exist in the &amp;quot;TABLE_ID&amp;quot; table:&lt;br /&gt;
    | COLUMN_HEADER1 | COLUMN_HEADER2 |&lt;br /&gt;
    | VALUE_IN_ROW_1 | VALUE_IN_ROW_1 |&lt;br /&gt;
    | VALUE_IN_ROW_2 | VALUE_IN_ROW_2 |&lt;br /&gt;
&lt;br /&gt;
==== Advanced use cases ====&lt;br /&gt;
Most of the time the usage of existing step definitions is straight forward. However, there are some exceptions were it might get complicated. Some of them are listed here:&lt;br /&gt;
&lt;br /&gt;
====== Uploading files ======&lt;br /&gt;
Note than some tests requires files to be uploaded, in this case&lt;br /&gt;
* The &#039;&#039;&#039;I upload &amp;quot;FILEPATH_STRING&amp;quot; file to &amp;quot;FILEPICKER_FIELD_STRING&amp;quot; filepicker&#039;&#039;&#039; step can be used when located in the form page&lt;br /&gt;
* The file to upload should be included along with the Moodle codebase in COMPONENTNAME/tests/fixtures/*&lt;br /&gt;
* The file to upload is specified by it&#039;s path, which should be relative to the codebase root (&#039;&#039;&#039;lib/tests/fixtures/users.csv&#039;&#039;&#039; for example) &lt;br /&gt;
* &#039;&#039;&#039;/&#039;&#039;&#039; should be used as directory separator and the file names can not include this &#039;&#039;&#039;/&#039;&#039;&#039; character as all of them would be converted to the OS-dependant directory separator to maintain the compatibility with Windows systems.&lt;br /&gt;
* The scenarios that includes files uploading should be tagged using the &#039;&#039;&#039;@_file_upload&#039;&#039;&#039; tag&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@editor @editor_atto @atto @atto_media @_file_upload&lt;br /&gt;
Feature: Add media to Atto&lt;br /&gt;
  To write rich text - I need to add media.&lt;br /&gt;
&lt;br /&gt;
  Background:&lt;br /&gt;
    Given I log in as &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I follow &amp;quot;Manage private files...&amp;quot;&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.webm&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.mp4&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.png&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-en.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-sv.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I click on &amp;quot;Save changes&amp;quot; &amp;quot;button&amp;quot;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Field groups ======&lt;br /&gt;
This section describes how you can use the step definitions&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
...&lt;br /&gt;
When I set the field &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot; to &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
for field groups. Examples for such field groups are the duration field or the date_time_selector. These are not displayed as one single input field within the front-end but consist of multiple input fields within one row.&lt;br /&gt;
You can access each single input field of a group using &lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
identifierOfYourField[keyOfTheSpecificInput]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Examples would be:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   21   |&lt;br /&gt;
  | myDate[month]           |   12   |&lt;br /&gt;
  | myDate[hour]            |   14   |&lt;br /&gt;
  | myDuration[number]      |   10   |&lt;br /&gt;
  | myDuration[unit]        | days   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Human-readable and relative dates ======&lt;br /&gt;
When testing plugins with deadlines, for instance for submissions, it is often necessary to set certain time values to dates relative to today.&lt;br /&gt;
You can specify a relative time enclosed within two ## blocks. For example:&lt;br /&gt;
* ## yesterday ##&lt;br /&gt;
* ## 2 days ago ##&lt;br /&gt;
You can use everything according to [http://php.net/manual/en/datetime.formats.php].&lt;br /&gt;
&lt;br /&gt;
Especially useful are the relative formats from: [http://php.net/manual/en/datetime.formats.relative.php]&lt;br /&gt;
&lt;br /&gt;
Additionally, you can specify a format you want the date to be returned into:&lt;br /&gt;
* ## yesterday ## myformat ##&lt;br /&gt;
These formats can be used as outlined in [http://php.net/manual/en/function.date.php].&lt;br /&gt;
This can be combined with the field groups:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   ## yesterday ## j ##   |&lt;br /&gt;
  | myDate[month]           |   ## yesterday ## n ##   |&lt;br /&gt;
  | myDate[year]            |   ## yesterday ## Y ##   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Writing your own steps ====&lt;br /&gt;
&lt;br /&gt;
Sometimes, you will need to set up data that is specific to your plugin, or perform steps that are specific to your plugin&#039;s UI. In this case it may be necessary to [[Writing_new_acceptance_test_step_definitions|write new step definitions]]&lt;br /&gt;
&lt;br /&gt;
===== Custom entity generators =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, it is possible to extend the &#039;&#039;Given the following &amp;quot;entites&amp;quot; exist&#039;&#039; step to support your plugin&#039;s data generators. This avoids having to write new whole&lt;br /&gt;
new behat step definitions for your plugin, and allows you to re-use data generators between PHPUnit and Behat tests.&lt;br /&gt;
&lt;br /&gt;
Full documentation of this process and all available options can be found in the [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/lib/behat/classes/behat_generator_base.php#L33 PHPDoc for behat_generator_base]. A core example of this can be found in [https://github.com/moodle/moodle/tree/master/mod/quiz/tests/generator /mod/quiz/tests/generator] and [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/mod/quiz/tests/behat/quiz_reset.feature#L51 quiz_reset.feature]. What follows is a simple example.&lt;br /&gt;
&lt;br /&gt;
To begin, you need a [[Writing_PHPUnit_tests#Generators|generator]] in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/lib.php. If you are generating a type of entity called &amp;quot;thing&amp;quot;, your generator will need a method called create_thing, which accepts an object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class local_myplugin_generator extends component_generator_base {&lt;br /&gt;
    public function create_thing($thing) {&lt;br /&gt;
        global $DB;&lt;br /&gt;
        $DB-&amp;gt;insert_record(&#039;local_myplugin_things&#039;, $thing);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, you will need to define your behat generator in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/behat_&#039;&#039;your_plugin&#039;&#039;_generator.php, with the method get_createable_entitites():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class behat_local_myplugin_generator extends behat_generator_base {&lt;br /&gt;
&lt;br /&gt;
    protected function get_creatable_entities(): array {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;things&#039; =&amp;gt; [&lt;br /&gt;
                &#039;datagenerator&#039; =&amp;gt; &#039;thing&#039;,&lt;br /&gt;
                &#039;required&#039; =&amp;gt; [&#039;name&#039;]&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &#039;datagenerator&#039; value refers to the method in the generator class that we are calling, in this case &#039;&#039;create_thing()&#039;&#039;. The outer array key is the entity name we will use in the behat step, in this case &#039;&#039;Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Now, in your behat test, you can have a step like this, which will generate 2 &#039;&#039;things&#039;&#039;, the first with the name &amp;quot;thing1&amp;quot; and the second with the name &amp;quot;thing2&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code gherkin&amp;gt;&lt;br /&gt;
Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist:&lt;br /&gt;
  | name   |&lt;br /&gt;
  | thing1 |&lt;br /&gt;
  | thing2 |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Good practice ==&lt;br /&gt;
&lt;br /&gt;
=== Test one thing per scenario ===&lt;br /&gt;
&lt;br /&gt;
The ideal that you should strive for, is that each scenario tests just one specific bit of functionality. Therefore, if one test fails, the scenario name should tell you exactly what the bug is. Also, any bug should cause just one scenario to fail, not lots of unrelated ones. If you can achieve this, then the idea is that it minimises the time from seeing a test fail to having fixed the bug that was detected. Of course, this ideal is not always achievable, but in my experience it is worth striving for.&lt;br /&gt;
&lt;br /&gt;
Note that this also implies that the Given, When and Then keywords should be used only once per scenario.&lt;br /&gt;
&lt;br /&gt;
=== Set-up (Given) should not use the UI ===&lt;br /&gt;
&lt;br /&gt;
The setup is not what you are really testing here. Therefore, it should be as quick and reliable as possible. The way to achieve this is with steps like &amp;lt;tt&amp;gt;And the following &amp;quot;Thing&amp;quot; exist:&amp;lt;/tt&amp;gt; which directly insert the data into the database. If necessary, write extra steps for your plugin to setup the things you need.&lt;br /&gt;
&lt;br /&gt;
=== Don&#039;t use XPath or CSS selectors - fix your Accessibility bugs ===&lt;br /&gt;
&lt;br /&gt;
If, the only way you can identify something in the page that you want to manipulate is with a step like &amp;lt;tt&amp;gt;I set the field with xpath &amp;quot;//textarea[contains(@name, &#039;answer&#039;)]&amp;quot; to &amp;quot;frog&amp;quot;&amp;lt;/tt&amp;gt;, then this is probably the sign that you have an Accessibility bug, because Behat accesses the page very like a screen-reader user would.&lt;br /&gt;
&lt;br /&gt;
You should be able to refer to things with steps like &amp;lt;tt&amp;gt;I set the field &amp;quot;Answer&amp;quot; to &amp;quot;frog&amp;quot;&#039;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;I click on &amp;quot;True&amp;quot; &amp;quot;radio&amp;quot; in the &amp;quot;First question&amp;quot; &amp;quot;question&amp;quot;&amp;lt;/tt&amp;gt;. If not, you should probably think about fixing the accessibility bug, rather than resorting to unreadable selectors in your Behat test.&lt;br /&gt;
&lt;br /&gt;
=== When you define more steps in your plugin, make it clear they come from your plugin ===&lt;br /&gt;
&lt;br /&gt;
When defining new Step definitions in your plugin, try to make sure the step name identifies it as belonging to your plugin. So, don&#039;t make a step called &amp;lt;tt&amp;gt;I disable UI plugins&amp;lt;/tt&amp;gt;. Call it something like &amp;lt;tt&amp;gt;I disable UI plugins in the CodeRunner question type&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57263</id>
		<title>Writing acceptance tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57263"/>
		<updated>2020-04-07T14:34:26Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This documentation gives some hints, how to write behat tests for core and for plugins. The focus of the documentation is on behat tests for plugins. They are written in some kind of natural language and describe how the front-end of moodle should behave, when a user interacts with it.&lt;br /&gt;
&lt;br /&gt;
Each test consists of a set of so called steps in a GIVEN, WHEN, THEN style:&lt;br /&gt;
* &#039;&#039;&#039;GIVEN&#039;&#039;&#039;: These steps outline the state of your moodle platform at the start of your test. Here you can create users, courses and plugin instances.&lt;br /&gt;
* &#039;&#039;&#039;WHEN&#039;&#039;&#039;: These steps usually execute the functionality of your plugin, which is under test.&lt;br /&gt;
* &#039;&#039;&#039;THEN&#039;&#039;&#039;: These steps describe the expected behaviour of your plugin. They usually check if different elements can or can&#039;t be seen.&lt;br /&gt;
This matches the standard [http://xunitpatterns.com/Four%20Phase%20Test.html Four-phase test pattern]. The fourth phase is &#039;tear-down&#039; which we don&#039;t need because Behat automatically cleans up after each test.&lt;br /&gt;
&lt;br /&gt;
To initialize and run your tests, please follow the instructions of [[Running_acceptance_test]].&lt;br /&gt;
&lt;br /&gt;
== Create your own tests ==&lt;br /&gt;
Behat tests are located within the directory tests/behat of your plugin.&lt;br /&gt;
The different tests are defined in files with the ending *.feature.&lt;br /&gt;
First, you have to define the header of your test:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@mod @mod_yourplugin @javascript&lt;br /&gt;
Feature: Here comes a description of your user story.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The tags on top of the feature description can be used to select specific test cases when running the tests.&lt;br /&gt;
The &#039;@javascript&#039; tag should only be used, if javascript is needed to execute your test. This is dependent on the step you will use in your definition.&lt;br /&gt;
Javascript tests are usually much slower than tests executed without javascript.&lt;br /&gt;
&lt;br /&gt;
Afterwards you can specify a scenario:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@javascript&lt;br /&gt;
  Scenario: Description of your scenario, which you want to test.&lt;br /&gt;
    When I log in as &amp;quot;student1&amp;quot;&lt;br /&gt;
    And I am on &amp;quot;Course 1&amp;quot; course homepage&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Again you can define specific tags. Afterwards you write the steps, which should be executed during your test.&lt;br /&gt;
&lt;br /&gt;
==== Multiple Scenarios ====&lt;br /&gt;
You can have an arbitrary amount of scenarios within a test. Please make sure they all belong to the same feature.&lt;br /&gt;
If you have certain steps, which should be executed for every scenario of a feature, you can define them using a background:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
Background:&lt;br /&gt;
    Given the following &amp;quot;courses&amp;quot; exist:&lt;br /&gt;
      | fullname | shortname | category | groupmode |&lt;br /&gt;
      | Course 1 | C1        | 0        | 1         |&lt;br /&gt;
    And the following &amp;quot;users&amp;quot; exist:&lt;br /&gt;
      | username | firstname | lastname | email |&lt;br /&gt;
      | teacher1 | Theo | Teacher | teacher1@example.com |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This is usually used, to define the different &#039;&#039;&#039;GIVEN&#039;&#039;&#039; steps.&lt;br /&gt;
&lt;br /&gt;
==== Use existing steps ====&lt;br /&gt;
There are different ways how to effectively browse the available existing steps:&lt;br /&gt;
&lt;br /&gt;
====== Moodle Administration ======&lt;br /&gt;
Moodle offers within its administration menu under Site Administration &amp;gt; Development &amp;gt; Acceptance Testing a complete and searchable list of all available step definitions.&lt;br /&gt;
However, make sure you installed the behat test site first!&lt;br /&gt;
&lt;br /&gt;
====== PhpStorm ======&lt;br /&gt;
In PhpStorm or IntelliJ you can install the behat extension. Then you get auto completions within feature files, which helps a lot during behat test development.&lt;br /&gt;
&lt;br /&gt;
==== Providing values to steps ====&lt;br /&gt;
Most of the steps requires values, there are methods to provide values to steps, the method depends on the step specification.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;A string/text&#039;&#039;&#039;; is the most common case, the texts are wrapped between double quotes (&amp;quot; character) you have to replace the info about the expected value for your value; for example something like &#039;&#039;&#039;I press &amp;quot;BUTTON_STRING&amp;quot;&#039;&#039;&#039; should become &#039;&#039;&#039;I press &amp;quot;Save and return to course&amp;quot;&#039;&#039;&#039;. If you want to add a string which contains a &amp;quot; character, you can escape it with \&amp;quot;, for example &#039;&#039;&#039;I fill the &amp;quot;Name&amp;quot; field with &amp;quot;Alan alias \&amp;quot;the legend\&amp;quot;&amp;quot;&#039;&#039;&#039;. You can identify this steps because they ends with &#039;&#039;&#039;_STRING&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A number&#039;&#039;&#039;; some steps requires numbers as values, to be more specific an undetermined number of digits from 0 to 9 (Natural numbers + 0) you can identify them because the expected value info string ends with &#039;&#039;&#039;_NUMBER&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A table&#039;&#039;&#039;; is a relation between values, the most common use of it is to fill forms. The steps which requires tables are easily identifiable because they finish with &#039;&#039;&#039;:&#039;&#039;&#039; The steps description gives info about what the table columns must contain, for example &#039;&#039;&#039;Fills a moodle form with field/value data&#039;&#039;&#039;. Here you don&#039;t need to escape the double quotes if you want to include them as part of the value.&lt;br /&gt;
* &#039;&#039;&#039;A PyString&#039;&#039;&#039;; is a multiline string, most commonly used to fill out forms when a newline is required. Like steps with tables, steps which require PyStrings will end with &amp;quot;:&amp;quot;&lt;br /&gt;
* &#039;&#039;&#039;A field value&#039;&#039;&#039;; There are many different field types, if an argument requires a field value the expected value will depend on the field type:&lt;br /&gt;
** Text-based fields: It expects the text. This includes textareas, input type text, input type password...&lt;br /&gt;
** Checkbox: It expects 1 to check and for checked and &amp;quot;&amp;quot; to uncheck or for unchecked&lt;br /&gt;
** Select: It expects the option text or the option value. In case you interact with a multi-select you should specify the options separating them with commas. For example: &#039;&#039;&#039;option1, option2, option3&#039;&#039;&#039;&lt;br /&gt;
** Radio: The text of the radio option&lt;br /&gt;
* &#039;&#039;&#039;A selector&#039;&#039;&#039;; there are steps that can be used with different kinds of elements, for example &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;link&amp;quot;&#039;&#039;&#039; or &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;button&amp;quot;&#039;&#039;&#039; this is a closed list of elements, they always works together with another argument, where you specify the locator (eg. the link text in a link) In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** field - for searching a field by its id, name, value or label&lt;br /&gt;
** link - for searching a link by its href, id, title, img alt or value&lt;br /&gt;
** button - for searching a button by its name, id, value, img alt or title&lt;br /&gt;
** link_or_button - for searching for both, links and buttons&lt;br /&gt;
** select - for searching a select field by its id, name or label&lt;br /&gt;
** checkbox - for searching a checkbox by its id, name, or label&lt;br /&gt;
** radio - for searching a radio button by its id, name, or label&lt;br /&gt;
** file - for searching a file input by its id, name, or label&lt;br /&gt;
** optgroup - for searching optgroup by its label&lt;br /&gt;
** option - for searching an option by its content&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** filemanager - for searching a filemanager by it&#039;s id or label&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all the page&#039;s ids&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** icon - for searching an icon by its title &lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
* &#039;&#039;&#039;A text selector&#039;&#039;&#039;; similar to a selector but those are the elements that returns an area of the DOM, they are useful in steps following the format &#039;&#039;&#039;... in the &amp;quot;Community finder&amp;quot; &amp;quot;block&amp;quot;&#039;&#039;&#039; where you are clicking or looking for some text inside a specific area. In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all the page&#039;s ids&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
&lt;br /&gt;
====== Checking table values ======&lt;br /&gt;
You can check if specific value exists or not in a table row/column by using:&lt;br /&gt;
* Then &amp;quot;STRING_IN_ROW&amp;quot; row &amp;quot;COLUMN_HEADER&amp;quot; column of &amp;quot;TABLE_ID&amp;quot; table should contain &amp;quot;VALUE_TO_CHECK&amp;quot;&lt;br /&gt;
* Then the following should exist in the &amp;quot;TABLE_ID&amp;quot; table:&lt;br /&gt;
    | COLUMN_HEADER1 | COLUMN_HEADER2 |&lt;br /&gt;
    | VALUE_IN_ROW_1 | VALUE_IN_ROW_1 |&lt;br /&gt;
    | VALUE_IN_ROW_2 | VALUE_IN_ROW_2 |&lt;br /&gt;
&lt;br /&gt;
==== Advanced use cases ====&lt;br /&gt;
Most of the time the usage of existing step definitions is straight forward. However, there are some exceptions were it might get complicated. Some of them are listed here:&lt;br /&gt;
&lt;br /&gt;
====== Uploading files ======&lt;br /&gt;
Note than some tests requires files to be uploaded, in this case&lt;br /&gt;
* The &#039;&#039;&#039;I upload &amp;quot;FILEPATH_STRING&amp;quot; file to &amp;quot;FILEPICKER_FIELD_STRING&amp;quot; filepicker&#039;&#039;&#039; step can be used when located in the form page&lt;br /&gt;
* The file to upload should be included along with the Moodle codebase in COMPONENTNAME/tests/fixtures/*&lt;br /&gt;
* The file to upload is specified by it&#039;s path, which should be relative to the codebase root (&#039;&#039;&#039;lib/tests/fixtures/users.csv&#039;&#039;&#039; for example) &lt;br /&gt;
* &#039;&#039;&#039;/&#039;&#039;&#039; should be used as directory separator and the file names can not include this &#039;&#039;&#039;/&#039;&#039;&#039; character as all of them would be converted to the OS-dependant directory separator to maintain the compatibility with Windows systems.&lt;br /&gt;
* The scenarios that includes files uploading should be tagged using the &#039;&#039;&#039;@_file_upload&#039;&#039;&#039; tag&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@editor @editor_atto @atto @atto_media @_file_upload&lt;br /&gt;
Feature: Add media to Atto&lt;br /&gt;
  To write rich text - I need to add media.&lt;br /&gt;
&lt;br /&gt;
  Background:&lt;br /&gt;
    Given I log in as &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I follow &amp;quot;Manage private files...&amp;quot;&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.webm&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.mp4&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.png&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-en.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-sv.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I click on &amp;quot;Save changes&amp;quot; &amp;quot;button&amp;quot;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Field groups ======&lt;br /&gt;
This section describes how you can use the step definitions&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
...&lt;br /&gt;
When I set the field &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot; to &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
for field groups. Examples for such field groups are the duration field or the date_time_selector. These are not displayed as one single input field within the front-end but consist of multiple input fields within one row.&lt;br /&gt;
You can access each single input field of a group using &lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
identifierOfYourField[keyOfTheSpecificInput]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Examples would be:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   21   |&lt;br /&gt;
  | myDate[month]           |   12   |&lt;br /&gt;
  | myDate[hour]            |   14   |&lt;br /&gt;
  | myDuration[number]      |   10   |&lt;br /&gt;
  | myDuration[unit]        | days   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Human-readable and relative dates ======&lt;br /&gt;
When testing plugins with deadlines, for instance for submissions, it is often necessary to set certain time values to dates relative to today.&lt;br /&gt;
You can specify a relative time enclosed within two ## blocks. For example:&lt;br /&gt;
* ## yesterday ##&lt;br /&gt;
* ## 2 days ago ##&lt;br /&gt;
You can use everything according to [http://php.net/manual/en/datetime.formats.php].&lt;br /&gt;
&lt;br /&gt;
Especially useful are the relative formats from: [http://php.net/manual/en/datetime.formats.relative.php]&lt;br /&gt;
&lt;br /&gt;
Additionally, you can specify a format you want the date to be returned into:&lt;br /&gt;
* ## yesterday ## myformat ##&lt;br /&gt;
These formats can be used as outlined in [http://php.net/manual/en/function.date.php].&lt;br /&gt;
This can be combined with the field groups:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   ## yesterday ## j ##   |&lt;br /&gt;
  | myDate[month]           |   ## yesterday ## n ##   |&lt;br /&gt;
  | myDate[year]            |   ## yesterday ## Y ##   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Writing your own steps ====&lt;br /&gt;
&lt;br /&gt;
Sometimes, you will need to set up data that is specific to your plugin, or perform steps that are specific to your plugin&#039;s UI. In this case it may be necessary to [[Writing_new_acceptance_test_step_definitions|write new step definitions]]&lt;br /&gt;
&lt;br /&gt;
===== Custom entity generators =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, it is possible to extend the &#039;&#039;Given the following &amp;quot;entites&amp;quot; exist&#039;&#039; step to support your plugin&#039;s data generators. This avoids having to write new whole&lt;br /&gt;
new behat step definitions for your plugin, and allows you to re-use data generators between PHPUnit and Behat tests.&lt;br /&gt;
&lt;br /&gt;
Full documentation of this process and all available options can be found in the [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/lib/behat/classes/behat_generator_base.php#L33 PHPDoc for behat_generator_base]. A core example of this can be found in [https://github.com/moodle/moodle/tree/master/mod/quiz/tests/generator /mod/quiz/tests/generator] and [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/mod/quiz/tests/behat/quiz_reset.feature#L51 quiz_reset.feature]. What follows is a simple example.&lt;br /&gt;
&lt;br /&gt;
To begin, you need a [[Writing_PHPUnit_tests#Generators|generator]] in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/lib.php. If you are generating a type of entity called &amp;quot;thing&amp;quot;, your generator will need a method called create_thing, which accepts an object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class local_myplugin_generator extends component_generator_base {&lt;br /&gt;
    public function create_thing($thing) {&lt;br /&gt;
        global $DB;&lt;br /&gt;
        $DB-&amp;gt;insert_record(&#039;local_myplugin_things&#039;, $thing);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, you will need to define your behat generator in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/behat_&#039;&#039;your_plugin&#039;&#039;_generator.php, with the method get_createable_entitites():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class behat_local_myplugin_generator extends behat_generator_base {&lt;br /&gt;
&lt;br /&gt;
    protected function get_creatable_entities(): array {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;things&#039; =&amp;gt; [&lt;br /&gt;
                &#039;datagenerator&#039; =&amp;gt; &#039;thing&#039;,&lt;br /&gt;
                &#039;required&#039; =&amp;gt; [&#039;name&#039;]&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &#039;datagenerator&#039; value refers to the method in the generator class that we are calling, in this case &#039;&#039;create_thing()&#039;&#039;. The outer array key is the entity name we will use in the behat step, in this case &#039;&#039;Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Now, in your behat test, you can have a step like this, which will generate 2 &#039;&#039;things&#039;&#039;, the first with the name &amp;quot;thing1&amp;quot; and the second with the name &amp;quot;thing2&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code gherkin&amp;gt;&lt;br /&gt;
Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exist:&lt;br /&gt;
  | name   |&lt;br /&gt;
  | thing1 |&lt;br /&gt;
  | thing2 |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Good practice ==&lt;br /&gt;
&lt;br /&gt;
=== Test one thing per scenario ===&lt;br /&gt;
&lt;br /&gt;
The ideal that you should strive for, is that each scenario tests just one specific bit of functionality. Therefore, if one test fails, the scenario name should tell you exactly what the bug is. Also, any bug should cause just one scenario to fail, not lots of unrelated ones. If you can achieve this, then the idea is that it minimises the time from seeing a test fail to having fixed the bug that was detected. Of course, this ideal is not always achievable, but in my experience it is worth striving for.&lt;br /&gt;
&lt;br /&gt;
=== Set-up (Given) should not use the UI ===&lt;br /&gt;
&lt;br /&gt;
The setup is not what you are really testing here. Therefore, it should be as quick and reliable as possible. The way to achieve this is with steps like &amp;lt;tt&amp;gt;And the following &amp;quot;Thing&amp;quot; exist:&amp;lt;/tt&amp;gt; which directly insert the data into the database. If necessary, write extra steps for your plugin to setup the things you need.&lt;br /&gt;
&lt;br /&gt;
=== Don&#039;t use XPath or CSS selectors - fix your Accessibility bugs ===&lt;br /&gt;
&lt;br /&gt;
If, the only way you can identify something in the page that you want to manipulate is with a step like &amp;lt;tt&amp;gt;I set the field with xpath &amp;quot;//textarea[contains(@name, &#039;answer&#039;)]&amp;quot; to &amp;quot;frog&amp;quot;&amp;lt;/tt&amp;gt;, then this is probably the sign that you have an Accessibility bug, because Behat accesses the page very like a screen-reader user would.&lt;br /&gt;
&lt;br /&gt;
You should be able to refer to things with steps like &amp;lt;tt&amp;gt;I set the field &amp;quot;Answer&amp;quot; to &amp;quot;frog&amp;quot;&#039;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;I click on &amp;quot;True&amp;quot; &amp;quot;radio&amp;quot; in the &amp;quot;First question&amp;quot; &amp;quot;question&amp;quot;&amp;lt;/tt&amp;gt;. If not, you should probably think about fixing the accessibility bug, rather than resorting to unreadable selectors in your Behat test.&lt;br /&gt;
&lt;br /&gt;
=== When you define more steps in your plugin, make it clear they come from your plugin ===&lt;br /&gt;
&lt;br /&gt;
When defining new Step definitions in your plugin, try to make sure the step name identifies it as belonging to your plugin. So, don&#039;t make a step called &amp;lt;tt&amp;gt;I disable UI plugins&amp;lt;/tt&amp;gt;. Call it something like &amp;lt;tt&amp;gt;I disable UI plugins in the CodeRunner question type&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57262</id>
		<title>Writing acceptance tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_acceptance_tests&amp;diff=57262"/>
		<updated>2020-04-07T14:33:20Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Add custom step and generator documentation&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This documentation gives some hints, how to write behat tests for core and for plugins. The focus of the documentation is on behat tests for plugins. They are written in some kind of natural language and describe how the front-end of moodle should behave, when a user interacts with it.&lt;br /&gt;
&lt;br /&gt;
Each test consists of a set of so called steps in a GIVEN, WHEN, THEN style:&lt;br /&gt;
* &#039;&#039;&#039;GIVEN&#039;&#039;&#039;: These steps outline the state of your moodle platform at the start of your test. Here you can create users, courses and plugin instances.&lt;br /&gt;
* &#039;&#039;&#039;WHEN&#039;&#039;&#039;: These steps usually execute the functionality of your plugin, which is under test.&lt;br /&gt;
* &#039;&#039;&#039;THEN&#039;&#039;&#039;: These steps describe the expected behaviour of your plugin. They usually check if different elements can or can&#039;t be seen.&lt;br /&gt;
This matches the standard [http://xunitpatterns.com/Four%20Phase%20Test.html Four-phase test pattern]. The fourth phase is &#039;tear-down&#039; which we don&#039;t need because Behat automatically cleans up after each test.&lt;br /&gt;
&lt;br /&gt;
To initialize and run your tests, please follow the instructions of [[Running_acceptance_test]].&lt;br /&gt;
&lt;br /&gt;
== Create your own tests ==&lt;br /&gt;
Behat tests are located within the directory tests/behat of your plugin.&lt;br /&gt;
The different tests are defined in files with the ending *.feature.&lt;br /&gt;
First, you have to define the header of your test:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@mod @mod_yourplugin @javascript&lt;br /&gt;
Feature: Here comes a description of your user story.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The tags on top of the feature description can be used to select specific test cases when running the tests.&lt;br /&gt;
The &#039;@javascript&#039; tag should only be used, if javascript is needed to execute your test. This is dependent on the step you will use in your definition.&lt;br /&gt;
Javascript tests are usually much slower than tests executed without javascript.&lt;br /&gt;
&lt;br /&gt;
Afterwards you can specify a scenario:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@javascript&lt;br /&gt;
  Scenario: Description of your scenario, which you want to test.&lt;br /&gt;
    When I log in as &amp;quot;student1&amp;quot;&lt;br /&gt;
    And I am on &amp;quot;Course 1&amp;quot; course homepage&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Again you can define specific tags. Afterwards you write the steps, which should be executed during your test.&lt;br /&gt;
&lt;br /&gt;
==== Multiple Scenarios ====&lt;br /&gt;
You can have an arbitrary amount of scenarios within a test. Please make sure they all belong to the same feature.&lt;br /&gt;
If you have certain steps, which should be executed for every scenario of a feature, you can define them using a background:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
Background:&lt;br /&gt;
    Given the following &amp;quot;courses&amp;quot; exist:&lt;br /&gt;
      | fullname | shortname | category | groupmode |&lt;br /&gt;
      | Course 1 | C1        | 0        | 1         |&lt;br /&gt;
    And the following &amp;quot;users&amp;quot; exist:&lt;br /&gt;
      | username | firstname | lastname | email |&lt;br /&gt;
      | teacher1 | Theo | Teacher | teacher1@example.com |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This is usually used, to define the different &#039;&#039;&#039;GIVEN&#039;&#039;&#039; steps.&lt;br /&gt;
&lt;br /&gt;
==== Use existing steps ====&lt;br /&gt;
There are different ways how to effectively browse the available existing steps:&lt;br /&gt;
&lt;br /&gt;
====== Moodle Administration ======&lt;br /&gt;
Moodle offers within its administration menu under Site Administration &amp;gt; Development &amp;gt; Acceptance Testing a complete and searchable list of all available step definitions.&lt;br /&gt;
However, make sure you installed the behat test site first!&lt;br /&gt;
&lt;br /&gt;
====== PhpStorm ======&lt;br /&gt;
In PhpStorm or IntelliJ you can install the behat extension. Then you get auto completions within feature files, which helps a lot during behat test development.&lt;br /&gt;
&lt;br /&gt;
==== Providing values to steps ====&lt;br /&gt;
Most of the steps requires values, there are methods to provide values to steps, the method depends on the step specification.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;A string/text&#039;&#039;&#039;; is the most common case, the texts are wrapped between double quotes (&amp;quot; character) you have to replace the info about the expected value for your value; for example something like &#039;&#039;&#039;I press &amp;quot;BUTTON_STRING&amp;quot;&#039;&#039;&#039; should become &#039;&#039;&#039;I press &amp;quot;Save and return to course&amp;quot;&#039;&#039;&#039;. If you want to add a string which contains a &amp;quot; character, you can escape it with \&amp;quot;, for example &#039;&#039;&#039;I fill the &amp;quot;Name&amp;quot; field with &amp;quot;Alan alias \&amp;quot;the legend\&amp;quot;&amp;quot;&#039;&#039;&#039;. You can identify this steps because they ends with &#039;&#039;&#039;_STRING&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A number&#039;&#039;&#039;; some steps requires numbers as values, to be more specific an undetermined number of digits from 0 to 9 (Natural numbers + 0) you can identify them because the expected value info string ends with &#039;&#039;&#039;_NUMBER&#039;&#039;&#039;&lt;br /&gt;
* &#039;&#039;&#039;A table&#039;&#039;&#039;; is a relation between values, the most common use of it is to fill forms. The steps which requires tables are easily identifiable because they finish with &#039;&#039;&#039;:&#039;&#039;&#039; The steps description gives info about what the table columns must contain, for example &#039;&#039;&#039;Fills a moodle form with field/value data&#039;&#039;&#039;. Here you don&#039;t need to escape the double quotes if you want to include them as part of the value.&lt;br /&gt;
* &#039;&#039;&#039;A PyString&#039;&#039;&#039;; is a multiline string, most commonly used to fill out forms when a newline is required. Like steps with tables, steps which require PyStrings will end with &amp;quot;:&amp;quot;&lt;br /&gt;
* &#039;&#039;&#039;A field value&#039;&#039;&#039;; There are many different field types, if an argument requires a field value the expected value will depend on the field type:&lt;br /&gt;
** Text-based fields: It expects the text. This includes textareas, input type text, input type password...&lt;br /&gt;
** Checkbox: It expects 1 to check and for checked and &amp;quot;&amp;quot; to uncheck or for unchecked&lt;br /&gt;
** Select: It expects the option text or the option value. In case you interact with a multi-select you should specify the options separating them with commas. For example: &#039;&#039;&#039;option1, option2, option3&#039;&#039;&#039;&lt;br /&gt;
** Radio: The text of the radio option&lt;br /&gt;
* &#039;&#039;&#039;A selector&#039;&#039;&#039;; there are steps that can be used with different kinds of elements, for example &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;link&amp;quot;&#039;&#039;&#039; or &#039;&#039;&#039;I click on &amp;quot;User Name&amp;quot; &amp;quot;button&amp;quot;&#039;&#039;&#039; this is a closed list of elements, they always works together with another argument, where you specify the locator (eg. the link text in a link) In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** field - for searching a field by its id, name, value or label&lt;br /&gt;
** link - for searching a link by its href, id, title, img alt or value&lt;br /&gt;
** button - for searching a button by its name, id, value, img alt or title&lt;br /&gt;
** link_or_button - for searching for both, links and buttons&lt;br /&gt;
** select - for searching a select field by its id, name or label&lt;br /&gt;
** checkbox - for searching a checkbox by its id, name, or label&lt;br /&gt;
** radio - for searching a radio button by its id, name, or label&lt;br /&gt;
** file - for searching a file input by its id, name, or label&lt;br /&gt;
** optgroup - for searching optgroup by its label&lt;br /&gt;
** option - for searching an option by its content&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** filemanager - for searching a filemanager by it&#039;s id or label&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all the page&#039;s ids&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** icon - for searching an icon by its title &lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
* &#039;&#039;&#039;A text selector&#039;&#039;&#039;; similar to a selector but those are the elements that returns an area of the DOM, they are useful in steps following the format &#039;&#039;&#039;... in the &amp;quot;Community finder&amp;quot; &amp;quot;block&amp;quot;&#039;&#039;&#039; where you are clicking or looking for some text inside a specific area. In the &#039;Acceptance testing&#039; interface you can see a drop-down menu to select one of these options:&lt;br /&gt;
** dialogue - for searching a dialogue with the specified header text&lt;br /&gt;
** block - for searching a Moodle block by it&#039;s English name or it&#039;s frankenstyle name&lt;br /&gt;
** section - for searching for a section on a course page by it&#039;s title or its written out date (e.g. &amp;quot;1 January - 7 January&amp;quot;). Use &amp;quot;frontpage&amp;quot; &amp;quot;section&amp;quot; for the frontpage section if it has no title (default)&lt;br /&gt;
** activity - for searching for an activity module in a course list by it&#039;s title&lt;br /&gt;
** region - for searching a Moodle page region with that id, in fact it works with all the page&#039;s ids&lt;br /&gt;
** table_row - for searching a table row which contains the specified text&lt;br /&gt;
** table - for searching a table by its id or caption&lt;br /&gt;
** fieldset - for searching a fieldset by it&#039;s id or legend&lt;br /&gt;
** css_element - for searching an element by its CSS selector&lt;br /&gt;
** xpath_element - for searching an element by its XPath&lt;br /&gt;
&lt;br /&gt;
====== Checking table values ======&lt;br /&gt;
You can check if specific value exists or not in a table row/column by using:&lt;br /&gt;
* Then &amp;quot;STRING_IN_ROW&amp;quot; row &amp;quot;COLUMN_HEADER&amp;quot; column of &amp;quot;TABLE_ID&amp;quot; table should contain &amp;quot;VALUE_TO_CHECK&amp;quot;&lt;br /&gt;
* Then the following should exist in the &amp;quot;TABLE_ID&amp;quot; table:&lt;br /&gt;
    | COLUMN_HEADER1 | COLUMN_HEADER2 |&lt;br /&gt;
    | VALUE_IN_ROW_1 | VALUE_IN_ROW_1 |&lt;br /&gt;
    | VALUE_IN_ROW_2 | VALUE_IN_ROW_2 |&lt;br /&gt;
&lt;br /&gt;
==== Advanced use cases ====&lt;br /&gt;
Most of the time the usage of existing step definitions is straight forward. However, there are some exceptions were it might get complicated. Some of them are listed here:&lt;br /&gt;
&lt;br /&gt;
====== Uploading files ======&lt;br /&gt;
Note than some tests requires files to be uploaded, in this case&lt;br /&gt;
* The &#039;&#039;&#039;I upload &amp;quot;FILEPATH_STRING&amp;quot; file to &amp;quot;FILEPICKER_FIELD_STRING&amp;quot; filepicker&#039;&#039;&#039; step can be used when located in the form page&lt;br /&gt;
* The file to upload should be included along with the Moodle codebase in COMPONENTNAME/tests/fixtures/*&lt;br /&gt;
* The file to upload is specified by it&#039;s path, which should be relative to the codebase root (&#039;&#039;&#039;lib/tests/fixtures/users.csv&#039;&#039;&#039; for example) &lt;br /&gt;
* &#039;&#039;&#039;/&#039;&#039;&#039; should be used as directory separator and the file names can not include this &#039;&#039;&#039;/&#039;&#039;&#039; character as all of them would be converted to the OS-dependant directory separator to maintain the compatibility with Windows systems.&lt;br /&gt;
* The scenarios that includes files uploading should be tagged using the &#039;&#039;&#039;@_file_upload&#039;&#039;&#039; tag&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
@editor @editor_atto @atto @atto_media @_file_upload&lt;br /&gt;
Feature: Add media to Atto&lt;br /&gt;
  To write rich text - I need to add media.&lt;br /&gt;
&lt;br /&gt;
  Background:&lt;br /&gt;
    Given I log in as &amp;quot;admin&amp;quot;&lt;br /&gt;
    And I follow &amp;quot;Manage private files...&amp;quot;&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.webm&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.mp4&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/moodle-logo.png&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-en.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I upload &amp;quot;lib/editor/atto/tests/fixtures/pretty-good-sv.vtt&amp;quot; file to &amp;quot;Files&amp;quot; filemanager&lt;br /&gt;
    And I click on &amp;quot;Save changes&amp;quot; &amp;quot;button&amp;quot;&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Field groups ======&lt;br /&gt;
This section describes how you can use the step definitions&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
...&lt;br /&gt;
When I set the field &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot; to &amp;quot;([^&amp;quot;]|\&amp;quot;*)&amp;quot;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
for field groups. Examples for such field groups are the duration field or the date_time_selector. These are not displayed as one single input field within the front-end but consist of multiple input fields within one row.&lt;br /&gt;
You can access each single input field of a group using &lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
identifierOfYourField[keyOfTheSpecificInput]&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Examples would be:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   21   |&lt;br /&gt;
  | myDate[month]           |   12   |&lt;br /&gt;
  | myDate[hour]            |   14   |&lt;br /&gt;
  | myDuration[number]      |   10   |&lt;br /&gt;
  | myDuration[unit]        | days   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Human-readable and relative dates ======&lt;br /&gt;
When testing plugins with deadlines, for instance for submissions, it is often necessary to set certain time values to dates relative to today.&lt;br /&gt;
You can specify a relative time enclosed within two ## blocks. For example:&lt;br /&gt;
* ## yesterday ##&lt;br /&gt;
* ## 2 days ago ##&lt;br /&gt;
You can use everything according to [http://php.net/manual/en/datetime.formats.php].&lt;br /&gt;
&lt;br /&gt;
Especially useful are the relative formats from: [http://php.net/manual/en/datetime.formats.relative.php]&lt;br /&gt;
&lt;br /&gt;
Additionally, you can specify a format you want the date to be returned into:&lt;br /&gt;
* ## yesterday ## myformat ##&lt;br /&gt;
These formats can be used as outlined in [http://php.net/manual/en/function.date.php].&lt;br /&gt;
This can be combined with the field groups:&lt;br /&gt;
&amp;lt;code behat&amp;gt;&lt;br /&gt;
When I set the following fields to these values:&lt;br /&gt;
  | myDate[day]             |   ## yesterday ## j ##   |&lt;br /&gt;
  | myDate[month]           |   ## yesterday ## n ##   |&lt;br /&gt;
  | myDate[year]            |   ## yesterday ## Y ##   |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Writing your own steps ====&lt;br /&gt;
&lt;br /&gt;
Sometimes, you will need to set up data that is specific to your plugin, or perform steps that are specific to your plugin&#039;s UI. In this case it may be necessary to [[Writing_new_acceptance_test_step_definitions|write new step definitions]]&lt;br /&gt;
&lt;br /&gt;
===== Custom entity generators =====&lt;br /&gt;
&lt;br /&gt;
From Moodle 3.8 onwards, it is possible to extend the &#039;&#039;Given the following &amp;quot;entites&amp;quot; exist&#039;&#039; step to support your plugin&#039;s data generators. This avoids having to write new whole&lt;br /&gt;
new behat step definitions for your plugin, and allows you to re-use data generators between PHPUnit and Behat tests.&lt;br /&gt;
&lt;br /&gt;
Full documentation of this process and all available options can be found in the [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/lib/behat/classes/behat_generator_base.php#L33 PHPDoc for behat_generator_base]. A core example of this can be found in [https://github.com/moodle/moodle/tree/master/mod/quiz/tests/generator /mod/quiz/tests/generator] and [https://github.com/moodle/moodle/blob/1d4fdb0d1c60448104bc9eac79b5123863c67cbd/mod/quiz/tests/behat/quiz_reset.feature#L51 quiz_reset.feature]. What follows is a simple example.&lt;br /&gt;
&lt;br /&gt;
To begin, you need a [[Writing_PHPUnit_tests#Generators|generator]] in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/lib.php. If you are generating a type of entity called &amp;quot;thing&amp;quot;, your generator will need a method called create_thing, which accepts an object:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class local_myplugin_generator extends component_generator_base {&lt;br /&gt;
    public function create_thing($thing) {&lt;br /&gt;
        global $DB;&lt;br /&gt;
        $DB-&amp;gt;insert_record(&#039;local_myplugin_things&#039;, $thing);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next, you will need to define your behat generator in /&#039;&#039;your&#039;&#039;/&#039;&#039;plugin&#039;&#039;/tests/generator/behat_&#039;&#039;your_plugin&#039;&#039;_generator.php, with the method get_createable_entitites():&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class behat_local_myplugin_generator extends behat_generator_base {&lt;br /&gt;
&lt;br /&gt;
    protected function get_creatable_entities(): array {&lt;br /&gt;
        return [&lt;br /&gt;
            &#039;things&#039; =&amp;gt; [&lt;br /&gt;
                &#039;datagenerator&#039; =&amp;gt; &#039;thing&#039;,&lt;br /&gt;
                &#039;required&#039; =&amp;gt; [&#039;name&#039;]&lt;br /&gt;
            ],&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &#039;datagenerator&#039; value refers to the method in the generator class that we are calling, in this case &#039;&#039;create_thing()&#039;&#039;. The outer array key is the entity name we will use in the behat step, in this case &#039;&#039;Given the following &amp;quot;local_myplugin &amp;gt; things&amp;quot; exists&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Now, in your behat test, you can have a step like this, which will generate 2 &#039;&#039;things&#039;&#039;, the first with the name &amp;quot;thing1&amp;quot; and the second with the name &amp;quot;thing2&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code gherkin&amp;gt;&lt;br /&gt;
Given the following &amp;quot;local_myplugin &amp;gt; thing&amp;quot; exists:&lt;br /&gt;
  | name  |&lt;br /&gt;
  | thing1 |&lt;br /&gt;
  | thing2 |&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Good practice ==&lt;br /&gt;
&lt;br /&gt;
=== Test one thing per scenario ===&lt;br /&gt;
&lt;br /&gt;
The ideal that you should strive for, is that each scenario tests just one specific bit of functionality. Therefore, if one test fails, the scenario name should tell you exactly what the bug is. Also, any bug should cause just one scenario to fail, not lots of unrelated ones. If you can achieve this, then the idea is that it minimises the time from seeing a test fail to having fixed the bug that was detected. Of course, this ideal is not always achievable, but in my experience it is worth striving for.&lt;br /&gt;
&lt;br /&gt;
=== Set-up (Given) should not use the UI ===&lt;br /&gt;
&lt;br /&gt;
The setup is not what you are really testing here. Therefore, it should be as quick and reliable as possible. The way to achieve this is with steps like &amp;lt;tt&amp;gt;And the following &amp;quot;Thing&amp;quot; exist:&amp;lt;/tt&amp;gt; which directly insert the data into the database. If necessary, write extra steps for your plugin to setup the things you need.&lt;br /&gt;
&lt;br /&gt;
=== Don&#039;t use XPath or CSS selectors - fix your Accessibility bugs ===&lt;br /&gt;
&lt;br /&gt;
If, the only way you can identify something in the page that you want to manipulate is with a step like &amp;lt;tt&amp;gt;I set the field with xpath &amp;quot;//textarea[contains(@name, &#039;answer&#039;)]&amp;quot; to &amp;quot;frog&amp;quot;&amp;lt;/tt&amp;gt;, then this is probably the sign that you have an Accessibility bug, because Behat accesses the page very like a screen-reader user would.&lt;br /&gt;
&lt;br /&gt;
You should be able to refer to things with steps like &amp;lt;tt&amp;gt;I set the field &amp;quot;Answer&amp;quot; to &amp;quot;frog&amp;quot;&#039;&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;I click on &amp;quot;True&amp;quot; &amp;quot;radio&amp;quot; in the &amp;quot;First question&amp;quot; &amp;quot;question&amp;quot;&amp;lt;/tt&amp;gt;. If not, you should probably think about fixing the accessibility bug, rather than resorting to unreadable selectors in your Behat test.&lt;br /&gt;
&lt;br /&gt;
=== When you define more steps in your plugin, make it clear they come from your plugin ===&lt;br /&gt;
&lt;br /&gt;
When defining new Step definitions in your plugin, try to make sure the step name identifies it as belonging to your plugin. So, don&#039;t make a step called &amp;lt;tt&amp;gt;I disable UI plugins&amp;lt;/tt&amp;gt;. Call it something like &amp;lt;tt&amp;gt;I disable UI plugins in the CodeRunner question type&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=56515</id>
		<title>Setting up your development environment for the Moodle App</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=56515"/>
		<updated>2019-10-09T07:57:35Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* Windows: npm run ionic:serve hangs after &amp;quot;Starting &amp;#039;watch&amp;#039;&amp;quot; */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile}}&lt;br /&gt;
{{Moodle Mobile 3.5}}&lt;br /&gt;
&lt;br /&gt;
Note: These instructions do work (give or take) for the current 3.5.x version of the Moodle Mobile app.&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The structure of this page is&lt;br /&gt;
* the first part, up to the point where you get the &#039;ionic serve&#039; command to work is what you need to be able to do development on the app and test it in a browser.&lt;br /&gt;
* the second part is about how to build a version of the app that can be run on a device.&lt;br /&gt;
* then at the end is a list of troubleshooting advice. If you encouter a problem that is not already listed, please consider adding it.&lt;br /&gt;
&lt;br /&gt;
The majority of your development will be done using a browser. You will probably on begin to use an emulator once you need to simulate a real mobile device.&lt;br /&gt;
&lt;br /&gt;
If you are just [[Mobile support for plugins|adding mobile support to plugins]], remember that most of your development can be done using the online pre-built version at https://mobileapp.moodledemo.net/ (with Chrome or Chromium). However, if you want to be able to to write [[Acceptance_testing_for_the_mobile_app|automated acceptance tests for the app]] then you need to follow this page at least as far as getting the ionic serve command to work on this page.&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
&lt;br /&gt;
Windows tip: ingore any use of the sudo command below. Just use the command without it. Most things work that way, and if they don&#039;t try in a Powershell window that you have opened with &#039;Run as administrator ...&#039;.&lt;br /&gt;
&lt;br /&gt;
===Install a browser for development===&lt;br /&gt;
&lt;br /&gt;
We recommend Chromium browser (Google Chrome open source version) https://download-chromium.appspot.com/&lt;br /&gt;
Please, read [[Moodle_Mobile_development_using_Chrome_or_Chromium]] for more information&lt;br /&gt;
&lt;br /&gt;
===Install git===&lt;br /&gt;
&lt;br /&gt;
https://git-scm.com/book/en/v2/Getting-Started-Installing-Git&lt;br /&gt;
&lt;br /&gt;
===Install Node.js===&lt;br /&gt;
&lt;br /&gt;
On Linux we recommend you use [https://github.com/creationix/nvm nvm] - this lets you switch Node versions, and makes the install a bit easier than the official installation route.&lt;br /&gt;
&lt;br /&gt;
 nvm install latest&lt;br /&gt;
 nvm use 11.12.0 # Or whatever number nvm install reported.&lt;br /&gt;
&lt;br /&gt;
(Exact version is probably not critical. Moodle HQ devs report using both 8.12.x and 11.12.0 as of 2019-03-25.)&lt;br /&gt;
&lt;br /&gt;
On Windows we recommend you use https://github.com/coreybutler/nvm-windows. Same nvm commands as for Linux.&lt;br /&gt;
&lt;br /&gt;
On Mac users we recommend to install NodeJS via Macports.&lt;br /&gt;
&lt;br /&gt;
(It may seem simpler and easier to install directly from http://nodejs.org, but actually it seems to me more tricky to get that to work. If you have previously installed Node directly, and want to switch to nvm, you need to un-install node completely before installing nvm - or Google for trouble-shooting instructions, for example https://github.com/coreybutler/nvm-windows/issues/58#issuecomment-272608696.)&lt;br /&gt;
&lt;br /&gt;
===Install ionic===&lt;br /&gt;
 npm cache clean&lt;br /&gt;
 npm install -g cordova@8.1.2 ionic    # (If it throws an EACCESS error, run it again with sudo)&lt;br /&gt;
&lt;br /&gt;
===Install the npm required packages===&lt;br /&gt;
 sudo npm install -g gulp                      # (This will install gulp in a folder that should be in the PATH)&lt;br /&gt;
&lt;br /&gt;
===Windows only: Native build dependencies===&lt;br /&gt;
&lt;br /&gt;
node-gyp requires native build tools for your platform.  If you&#039;re developing on Mac or Linux, you&#039;ll probably have these already ([https://github.com/nodejs/node-gyp/blob/master/README.md refer to the docs if not]), on Windows, run the following command as administrator (in cmd or Powershell):&lt;br /&gt;
&lt;br /&gt;
 npm install --global --production windows-build-tools&lt;br /&gt;
&lt;br /&gt;
Warning! this installer can take a very, very long time to run. We were seeing it take hours. Literally. Be prepared to be very patient. Don&#039;t just make the natural assumption that it has crashed.&lt;br /&gt;
&lt;br /&gt;
===Mac only: Push notifications===&lt;br /&gt;
&lt;br /&gt;
Phonegap plugin push 1.9.0 requires CocoaPods to work on a Mac. The installation steps can be found in https://cocoapods.org/&lt;br /&gt;
&lt;br /&gt;
 sudo gem install cocoapods&lt;br /&gt;
 pod setup&lt;br /&gt;
&lt;br /&gt;
Please note that for compiling the app in Mac you need to open the &#039;&#039;&#039;Moodle.xcworkspace&#039;&#039;&#039; file, more information here: MOBILE-1970&lt;br /&gt;
&lt;br /&gt;
== Clone the app base code ==&lt;br /&gt;
&lt;br /&gt;
Clone the code base into a local directory in your computer.&lt;br /&gt;
It may be an idea to work from the integration branch rather than master.&lt;br /&gt;
 git clone https://github.com/moodlehq/moodlemobile2.git moodlemobiledirectory&lt;br /&gt;
 cd moodlemobiledirectory&lt;br /&gt;
 git checkout integration&lt;br /&gt;
&lt;br /&gt;
== Setup the environment ==&lt;br /&gt;
&lt;br /&gt;
Please, note that if you are creating a custom app with a custom URL scheme, you should edit the /package.json and /config.xml files and specify there your custom URL_SCHEME (replacing the existing value) and your [https://github.com/phonegap/phonegap-plugin-push/blob/master/docs/INSTALLATION.md GCMPN SENDER_ID].&lt;br /&gt;
&lt;br /&gt;
The following command must be run in the project&#039;s root folder:&lt;br /&gt;
 npm run setup&lt;br /&gt;
&lt;br /&gt;
If this fails, you can see what it is doing by looking at the &#039;scripts&#039; section in package.json. At the moment it is doing &amp;lt;tt&amp;gt;npm install &amp;amp;&amp;amp; cordova prepare &amp;amp;&amp;amp; gulp&amp;lt;/tt&amp;gt;. That is, running three commands back-to-back, but only carrying on if the previous one succeeds completely. You can try running the three commands separately. If you do, &amp;lt;tt&amp;gt;ionic serve&amp;lt;/tt&amp;gt; (see below) may work, even if &amp;lt;tt&amp;gt;cordova prepare&amp;lt;/tt&amp;gt; gives errors. You only really need &amp;lt;tt&amp;gt;cordova prepare&amp;lt;/tt&amp;gt; to work if you are going to go on and build the app.&lt;br /&gt;
&lt;br /&gt;
== Open the app in the browser ==&lt;br /&gt;
First start Chromium via the command line using the custom parameters as is mentioned here: [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
&lt;br /&gt;
and then, start the Ionic server:&lt;br /&gt;
&lt;br /&gt;
 ionic serve --browser chromium         # or chrome or whatever browser.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to open any browser you should run:&lt;br /&gt;
&lt;br /&gt;
 ionic serve -b&lt;br /&gt;
&lt;br /&gt;
Congratulations! Now that you have got to the piont where the &#039;ionic serve&#039; command works, you can start doing development on the app. You only need to read the rest of the page if you want to build packaged versions of the app.&lt;br /&gt;
&lt;br /&gt;
== Updating ionic and cordova ==&lt;br /&gt;
&lt;br /&gt;
 sudo npm update -g cordova&lt;br /&gt;
 sudo npm update -g ionic&lt;br /&gt;
&lt;br /&gt;
Update project platforms:&lt;br /&gt;
&lt;br /&gt;
 ionic cordova platform remove android&lt;br /&gt;
 ionic cordova platform remove ios&lt;br /&gt;
 ionic cordova platform add android&lt;br /&gt;
 ionic cordova platform add ios&lt;br /&gt;
&lt;br /&gt;
== Updating plugins ==&lt;br /&gt;
&lt;br /&gt;
 cordova plugin remove your_plugin_id&lt;br /&gt;
 cordova plugin add your_plugin_id&lt;br /&gt;
&lt;br /&gt;
== Building for Android and iOS ==&lt;br /&gt;
&lt;br /&gt;
Please see the guides below to be able to build for Android and iOS using the command line:&lt;br /&gt;
&lt;br /&gt;
Android: https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html&lt;br /&gt;
&lt;br /&gt;
iOS: https://cordova.apache.org/docs/en/latest/guide/platforms/ios/index.html&lt;br /&gt;
&lt;br /&gt;
If the build fails, please run &amp;lt;code&amp;gt;cordova requirements&amp;lt;/code&amp;gt; to check that you fulfilled all requirements for the platform.&lt;br /&gt;
&lt;br /&gt;
If you get errors while building, please see the Troubleshooting section below.&lt;br /&gt;
&lt;br /&gt;
If using &#039;&#039;&#039;Ubuntu&#039;&#039;&#039; you should install the packages: gradle and libgradle-android-plugin-java (and all its dependencies) to build.&lt;br /&gt;
&lt;br /&gt;
== Compiling using AOT ==&lt;br /&gt;
&lt;br /&gt;
Angular has 2 ways of compiling: JIT and AOT. Running &amp;quot;ionic serve&amp;quot; or &amp;quot;ionic build&amp;quot; compiles using JIT by default, which is faster to compile but the app takes longer to start.&lt;br /&gt;
&lt;br /&gt;
When building for release you should always compile using AOT, otherwise the app can take too long to start in some devices. The default AOT compiling causes some issues with the database activity and the [[Mobile support for plugins]], so you have to modify a couple of files in order to make this work.&lt;br /&gt;
&lt;br /&gt;
First you need to open the file: &#039;&#039;node_modules/@angular/platform-browser-dynamic/esm5/platform-browser-dynamic.js&#039;&#039;. Search the variable called &amp;quot;&#039;&#039;_NO_RESOURCE_LOADER&#039;&#039;&amp;quot;, you&#039;ll see it has a function named &amp;quot;&#039;&#039;get&#039;&#039;&amp;quot; with this line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
throw new Error(&amp;quot;No ResourceLoader implementation has been provided. Can&#039;t read the url \&amp;quot;&amp;quot; + url + &amp;quot;\&amp;quot;&amp;quot;);&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remove that line and put this code instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
        url = &#039;templates/&#039; + url;&lt;br /&gt;
&lt;br /&gt;
        var resolve;&lt;br /&gt;
        var reject;&lt;br /&gt;
        var promise = new Promise(function (res, rej) {&lt;br /&gt;
            resolve = res;&lt;br /&gt;
            reject = rej;&lt;br /&gt;
        });&lt;br /&gt;
        var xhr = new XMLHttpRequest();&lt;br /&gt;
        xhr.open(&#039;GET&#039;, url, true);&lt;br /&gt;
        xhr.responseType = &#039;text&#039;;&lt;br /&gt;
        xhr.onload = function () {&lt;br /&gt;
            // responseText is the old-school way of retrieving response (supported by IE8 &amp;amp; 9)&lt;br /&gt;
            // response/responseType properties were introduced in ResourceLoader Level2 spec (supported by IE10)&lt;br /&gt;
            var response = xhr.response || xhr.responseText;&lt;br /&gt;
            // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)&lt;br /&gt;
            var status = xhr.status === 1223 ? 204 : xhr.status;&lt;br /&gt;
            // fix status code when it is 0 (0 status is undocumented).&lt;br /&gt;
            // Occurs when accessing file resources or on Android 4.1 stock browser&lt;br /&gt;
            // while retrieving files from application cache.&lt;br /&gt;
            if (status === 0) {&lt;br /&gt;
                status = response ? 200 : 0;&lt;br /&gt;
            }&lt;br /&gt;
            if (200 &amp;lt;= status &amp;amp;&amp;amp; status &amp;lt;= 300) {&lt;br /&gt;
                resolve(response);&lt;br /&gt;
            }&lt;br /&gt;
            else {&lt;br /&gt;
                reject(&amp;quot;Failed to load &amp;quot; + url);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        xhr.onerror = function () { reject(&amp;quot;Failed to load &amp;quot; + url); };&lt;br /&gt;
        xhr.send();&lt;br /&gt;
        return promise;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We tried to replace the default loader with our own implementation, but we weren&#039;t able to make the compiler work so the only solution left was to modify the default one.&lt;br /&gt;
&lt;br /&gt;
Now you need to open the file: &#039;&#039;node_modules/@ionic/app-scripts/dist/util/config.js&#039;&#039;. In that file you need to remove the &#039;&#039;context.isProd&#039;&#039; condition from the options &#039;&#039;runMinifyJs&#039;&#039; and &#039;&#039;optimizeJs&#039;&#039;. So the final code for that part should be like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    context.runMinifyJs = [&lt;br /&gt;
        context.runMinifyJs,&lt;br /&gt;
        hasArg(&#039;--minifyJs&#039;)&lt;br /&gt;
    ].find(function (val) { return typeof val === &#039;boolean&#039;; });&lt;br /&gt;
    context.runMinifyCss = [&lt;br /&gt;
        context.runMinifyCss,&lt;br /&gt;
        context.isProd || hasArg(&#039;--minifyCss&#039;)&lt;br /&gt;
    ].find(function (val) { return typeof val === &#039;boolean&#039;; });&lt;br /&gt;
    context.optimizeJs = [&lt;br /&gt;
        context.optimizeJs,&lt;br /&gt;
        hasArg(&#039;--optimizeJs&#039;)&lt;br /&gt;
    ].find(function (val) { return typeof val === &#039;boolean&#039;; });&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We want to compile in production mode but without optimizing and minifying Javascript because that breaks our plugins support. However, Ionic doesn&#039;t let you do that, so the only option is to do this change.&lt;br /&gt;
&lt;br /&gt;
With these changes done you can now compile using production mode:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
npm run ionic:build -- --prod&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This command will generate the app files and put them inside &#039;&#039;www&#039;&#039; folder. If you now want to install that app in a real device you can run &amp;quot;&#039;&#039;cordova run android&#039;&#039;&amp;quot; or &amp;quot;&#039;&#039;cordova build ios&#039;&#039;&amp;quot; (please don&#039;t use &amp;quot;&#039;&#039;ionic cordova ...&#039;&#039;&amp;quot; or &amp;quot;&#039;&#039;ionic serve&#039;&#039;&amp;quot; because it will override your build files!).&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Strange NPM errors ===&lt;br /&gt;
&lt;br /&gt;
To get more debug output from npm commands, see https://docs.npmjs.com/misc/config#shorthands-and-other-cli-niceties. In particular try adding &amp;lt;tt&amp;gt;--loglevel verbose&amp;lt;/tt&amp;gt; (or &amp;lt;tt&amp;gt;--loglevel info&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--loglevel silly&amp;lt;/tt&amp;gt;) to the command-line.&lt;br /&gt;
&lt;br /&gt;
=== Error: libsass bindings not found. Try reinstalling node-sass? ===&lt;br /&gt;
&lt;br /&gt;
Please read: http://fettblog.eu/gulp-and-node4-first-aid/, alternatively you must be sure that you installed Node v0.12&lt;br /&gt;
&lt;br /&gt;
=== node-gyp\src\win_delay_load_hook.c(34): error C2373: &#039;__pfnDliNotifyHook2&#039;: redefinition; different type modifiers ===&lt;br /&gt;
&lt;br /&gt;
Try updating npm to the latest version using: &lt;br /&gt;
&lt;br /&gt;
  npm install -g npm@latest&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== com.android.dex.DexException: Multiple dex files define XXX ===&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations {&lt;br /&gt;
      all*.exclude group: &#039;com.android.support&#039;, module: &#039;support-v4&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
=== Could not resolve all dependencies for configuration &#039;:_debugCompile&#039;. ===&lt;br /&gt;
Open the Android SDK Manager and make sure you have installed: Android Support Repository, Android Support Library, Google Play Services and Google Repository.&lt;br /&gt;
&lt;br /&gt;
=== Could not find com.android.support:support-v4:XXX ===&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations.all {&lt;br /&gt;
      resolutionStrategy.force &#039;com.android.support:support-v4:24.0.0&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
=== ERROR: In &amp;lt;declare-styleable&amp;gt; FontFamilyFont, unable to find attribute android:font  ===&lt;br /&gt;
&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
 android {&lt;br /&gt;
     compileSdkVersion 26&lt;br /&gt;
     buildToolsVersion &amp;quot;26.0.1&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Error: Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.  ===&lt;br /&gt;
&lt;br /&gt;
1. Download Android studio - https://developer.android.com/studio/&lt;br /&gt;
&lt;br /&gt;
2. Copy the folder android-studio/plugins/android/lib/templates&lt;br /&gt;
&lt;br /&gt;
3. Paste in the folder android-sdk-folder/Sdk/tools&lt;br /&gt;
&lt;br /&gt;
=== Could not find com.android.support:support-v4:27.1.0 ===&lt;br /&gt;
&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and configure like this:&lt;br /&gt;
&lt;br /&gt;
  allprojects {&lt;br /&gt;
      repositories {&lt;br /&gt;
          jcenter()&lt;br /&gt;
          maven {&lt;br /&gt;
              url &amp;quot;https://maven.google.com&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
      }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
=== Error: not found: make ===&lt;br /&gt;
&lt;br /&gt;
If you see this error in Ubuntu, run &amp;lt;tt&amp;gt;sudo apt-get install build-essential&amp;lt;/tt&amp;gt; and retry.&lt;br /&gt;
&lt;br /&gt;
=== Current working directory is not a Cordova-based project. ===&lt;br /&gt;
&lt;br /&gt;
If you see this error during &amp;lt;tt&amp;gt;npm setup&amp;lt;/tt&amp;gt;, run &amp;lt;tt&amp;gt;mkdir www&amp;lt;/tt&amp;gt; and retry.&lt;br /&gt;
&lt;br /&gt;
=== ReferenceError: internalBinding is not defined ===&lt;br /&gt;
&lt;br /&gt;
This [https://stackoverflow.com/questions/53146394/node-app-fails-to-run-on-mojave-referenceerror-internalbinding-is-not-defined seems to be] an error with &#039;natives&#039; prior to 1.1.6. I fixed it using &amp;lt;tt&amp;gt;npm install natives@1.1.6&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== ReferenceError: internalBinding is not defined ===&lt;br /&gt;
&lt;br /&gt;
This [https://stackoverflow.com/questions/53146394/node-app-fails-to-run-on-mojave-referenceerror-internalbinding-is-not-defined seems to be] an error with &#039;natives&#039; prior to 1.1.6. I fixed it using &amp;lt;tt&amp;gt;npm install natives@1.1.6&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== npm update check failed===&lt;br /&gt;
&lt;br /&gt;
I got the error&lt;br /&gt;
&lt;br /&gt;
 │                   npm update check failed                   │&lt;br /&gt;
 │             Try running with sudo or get access             │ &lt;br /&gt;
 │            to the local update config store via             │&lt;br /&gt;
 │ sudo chown -R $USER:$(id -gn $USER) C:\Users\username\.config │&lt;br /&gt;
&lt;br /&gt;
on Windows because I installed too much as admin, and the suggested command does not work on Windows. The is to manually check the ownership of all the files in C:\Users\username\.config\configstore. In my case it was update-notifier-npm.json which got changed to be owned by Administrator.&lt;br /&gt;
&lt;br /&gt;
===Unhandled rejection Error: Command failed: C:\cygwin64\bin\git.EXE ...===&lt;br /&gt;
&lt;br /&gt;
You need to heed the advice at https://www.npmjs.com/package/npm#installing-on-cygwin. Cygwin users are not welcome in the Node world. However, you just need to ensure that Msysgit is on your windows path and that the cygwin bin folder is not. Then always use another shell like Powershell for your Moodle mobile development. (You don&#039;t need your Cygwin bin folder on the Windows path. It automatically gets added to the path when you lauch Cygwin bash.)&lt;br /&gt;
&lt;br /&gt;
===The product name change (&amp;lt;name&amp;gt; tag) in config.xml is not supported dynamically===&lt;br /&gt;
&lt;br /&gt;
According to Google, this happens when you create the iOS platform with a certain &amp;lt;name&amp;gt; and then you change that name in config.xml. It&#039;s weird that the installation process does that, it should create the platform with the right name unless it was changed manually.&lt;br /&gt;
&lt;br /&gt;
The solution seems to be removing and adding the iOS platform again:&lt;br /&gt;
&lt;br /&gt;
 ionic platform remove ios&lt;br /&gt;
 ionic platform add ios&lt;br /&gt;
&lt;br /&gt;
Note that this does not seem to prevent &amp;lt;tt&amp;gt;ionic --serve&amp;lt;/tt&amp;gt; from serving a working app if you run gulp after &amp;lt;tt&amp;gt;npm run setup&amp;lt;/tt&amp;gt; has failed with this error.&lt;br /&gt;
&lt;br /&gt;
===Failed to install &#039;cordova-plugin-file-transfer&#039;: CordovaError: Version of installed plugin: &amp;quot;cordova-plugin-file@4.3.3&amp;quot; does not satisfy dependency plugin requirement &amp;quot;cordova-plugin-file@&amp;gt;=5.0.0&amp;quot;.===&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;cordova-plugin-file&#039;&#039; version specified in config.xml is 6.0.1, for some reason the installation process installed a wrong version for that plugin. You can manually install the &#039;&#039;cordova-plugin-file&#039;&#039; plugin like this:&lt;br /&gt;
&lt;br /&gt;
 cordova plugin remove cordova-plugin-file&lt;br /&gt;
 cordova plugin add cordova-plugin-file@6.0.1&lt;br /&gt;
&lt;br /&gt;
Please notice that if there is any plugin installed that depends on &#039;&#039;cordova-plugin-file&#039;&#039; you&#039;ll have to remove and re-add them too.&lt;br /&gt;
&lt;br /&gt;
Note that this does not seem to prevent &amp;lt;tt&amp;gt;ionic --serve&amp;lt;/tt&amp;gt; from serving a working app if you run gulp after &amp;lt;tt&amp;gt;npm run setup&amp;lt;/tt&amp;gt; has failed with this error.&lt;br /&gt;
&lt;br /&gt;
===Cannot download &amp;quot;https://github.com/sass/node-sass/releases/download/v4.11.0/linux-x64-72_binding.node&amp;quot;===&lt;br /&gt;
Force using node 11, since node-sass for node 12 is not yet avalaible. See [https://tracker.moodle.org/browse/MOBILE-2999|MOBILE-2999] for more info.&lt;br /&gt;
&lt;br /&gt;
===Mac: linker code failed with exit code 1===&lt;br /&gt;
If you get this error when trying to build the Moodle app with XCode, some dependencies might not have installed correctly.&lt;br /&gt;
&lt;br /&gt;
Ensure you have followed the [https://docs.moodle.org/dev/Setting_up_your_development_environment_for_Moodle_Mobile_2#Mac_only:_Push_notifications| Mac only: Push notifications] steps above (particularly opening the .xcworkspace file rather than the .xcodeproj file). Then run the following:&lt;br /&gt;
&lt;br /&gt;
  npm run setup&lt;br /&gt;
  cd platforms/ios&lt;br /&gt;
  pod install&lt;br /&gt;
&lt;br /&gt;
Now try running the build again in XCode.&lt;br /&gt;
&lt;br /&gt;
===Windows: &amp;lt;code&amp;gt;npm run ionic:serve&amp;lt;/code&amp;gt; hangs after &amp;quot;Starting &#039;watch&#039;&amp;quot;===&lt;br /&gt;
This appears to have happened since the move to npx. Try running the npx commands generated by npm run directly in bash:&lt;br /&gt;
  npx gulp watch &amp;amp; npx ionic-app-scripts serve -b --devapp --address=0.0.0.0&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
http://ionicframework.com/docs/cli/&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=56512</id>
		<title>Setting up your development environment for the Moodle App</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=56512"/>
		<updated>2019-10-04T12:41:10Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Added workaround for ionic serve issue on Windows&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile}}&lt;br /&gt;
{{Moodle Mobile 3.5}}&lt;br /&gt;
&lt;br /&gt;
Note: These instructions do work (give or take) for the current 3.5.x version of the Moodle Mobile app.&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The structure of this page is&lt;br /&gt;
* the first part, up to the point where you get the &#039;ionic serve&#039; command to work is what you need to be able to do development on the app and test it in a browser.&lt;br /&gt;
* the second part is about how to build a version of the app that can be run on a device.&lt;br /&gt;
* then at the end is a list of troubleshooting advice. If you encouter a problem that is not already listed, please consider adding it.&lt;br /&gt;
&lt;br /&gt;
The majority of your development will be done using a browser. You will probably on begin to use an emulator once you need to simulate a real mobile device.&lt;br /&gt;
&lt;br /&gt;
If you are just [[Mobile support for plugins|adding mobile support to plugins]], remember that most of your development can be done using the online pre-built version at https://mobileapp.moodledemo.net/ (with Chrome or Chromium). However, if you want to be able to to write [[Acceptance_testing_for_the_mobile_app|automated acceptance tests for the app]] then you need to follow this page at least as far as getting the ionic serve command to work on this page.&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
&lt;br /&gt;
Windows tip: ingore any use of the sudo command below. Just use the command without it. Most things work that way, and if they don&#039;t try in a Powershell window that you have opened with &#039;Run as administrator ...&#039;.&lt;br /&gt;
&lt;br /&gt;
===Install a browser for development===&lt;br /&gt;
&lt;br /&gt;
We recommend Chromium browser (Google Chrome open source version) https://download-chromium.appspot.com/&lt;br /&gt;
Please, read [[Moodle_Mobile_development_using_Chrome_or_Chromium]] for more information&lt;br /&gt;
&lt;br /&gt;
===Install git===&lt;br /&gt;
&lt;br /&gt;
https://git-scm.com/book/en/v2/Getting-Started-Installing-Git&lt;br /&gt;
&lt;br /&gt;
===Install Node.js===&lt;br /&gt;
&lt;br /&gt;
On Linux we recommend you use [https://github.com/creationix/nvm nvm] - this lets you switch Node versions, and makes the install a bit easier than the official installation route.&lt;br /&gt;
&lt;br /&gt;
 nvm install latest&lt;br /&gt;
 nvm use 11.12.0 # Or whatever number nvm install reported.&lt;br /&gt;
&lt;br /&gt;
(Exact version is probably not critical. Moodle HQ devs report using both 8.12.x and 11.12.0 as of 2019-03-25.)&lt;br /&gt;
&lt;br /&gt;
On Windows we recommend you use https://github.com/coreybutler/nvm-windows. Same nvm commands as for Linux.&lt;br /&gt;
&lt;br /&gt;
On Mac users we recommend to install NodeJS via Macports.&lt;br /&gt;
&lt;br /&gt;
(It may seem simpler and easier to install directly from http://nodejs.org, but actually it seems to me more tricky to get that to work. If you have previously installed Node directly, and want to switch to nvm, you need to un-install node completely before installing nvm - or Google for trouble-shooting instructions, for example https://github.com/coreybutler/nvm-windows/issues/58#issuecomment-272608696.)&lt;br /&gt;
&lt;br /&gt;
===Install ionic===&lt;br /&gt;
 npm cache clean&lt;br /&gt;
 npm install -g cordova@8.1.2 ionic    # (If it throws an EACCESS error, run it again with sudo)&lt;br /&gt;
&lt;br /&gt;
===Install the npm required packages===&lt;br /&gt;
 sudo npm install -g gulp                      # (This will install gulp in a folder that should be in the PATH)&lt;br /&gt;
&lt;br /&gt;
===Windows only: Native build dependencies===&lt;br /&gt;
&lt;br /&gt;
node-gyp requires native build tools for your platform.  If you&#039;re developing on Mac or Linux, you&#039;ll probably have these already ([https://github.com/nodejs/node-gyp/blob/master/README.md refer to the docs if not]), on Windows, run the following command as administrator (in cmd or Powershell):&lt;br /&gt;
&lt;br /&gt;
 npm install --global --production windows-build-tools&lt;br /&gt;
&lt;br /&gt;
Warning! this installer can take a very, very long time to run. We were seeing it take hours. Literally. Be prepared to be very patient. Don&#039;t just make the natural assumption that it has crashed.&lt;br /&gt;
&lt;br /&gt;
===Mac only: Push notifications===&lt;br /&gt;
&lt;br /&gt;
Phonegap plugin push 1.9.0 requires CocoaPods to work on a Mac. The installation steps can be found in https://cocoapods.org/&lt;br /&gt;
&lt;br /&gt;
 sudo gem install cocoapods&lt;br /&gt;
 pod setup&lt;br /&gt;
&lt;br /&gt;
Please note that for compiling the app in Mac you need to open the &#039;&#039;&#039;Moodle.xcworkspace&#039;&#039;&#039; file, more information here: MOBILE-1970&lt;br /&gt;
&lt;br /&gt;
== Clone the app base code ==&lt;br /&gt;
&lt;br /&gt;
Clone the code base into a local directory in your computer.&lt;br /&gt;
It may be an idea to work from the integration branch rather than master.&lt;br /&gt;
 git clone https://github.com/moodlehq/moodlemobile2.git moodlemobiledirectory&lt;br /&gt;
 cd moodlemobiledirectory&lt;br /&gt;
 git checkout integration&lt;br /&gt;
&lt;br /&gt;
== Setup the environment ==&lt;br /&gt;
&lt;br /&gt;
Please, note that if you are creating a custom app with a custom URL scheme, you should edit the /package.json and /config.xml files and specify there your custom URL_SCHEME (replacing the existing value) and your [https://github.com/phonegap/phonegap-plugin-push/blob/master/docs/INSTALLATION.md GCMPN SENDER_ID].&lt;br /&gt;
&lt;br /&gt;
The following command must be run in the project&#039;s root folder:&lt;br /&gt;
 npm run setup&lt;br /&gt;
&lt;br /&gt;
If this fails, you can see what it is doing by looking at the &#039;scripts&#039; section in package.json. At the moment it is doing &amp;lt;tt&amp;gt;npm install &amp;amp;&amp;amp; cordova prepare &amp;amp;&amp;amp; gulp&amp;lt;/tt&amp;gt;. That is, running three commands back-to-back, but only carrying on if the previous one succeeds completely. You can try running the three commands separately. If you do, &amp;lt;tt&amp;gt;ionic serve&amp;lt;/tt&amp;gt; (see below) may work, even if &amp;lt;tt&amp;gt;cordova prepare&amp;lt;/tt&amp;gt; gives errors. You only really need &amp;lt;tt&amp;gt;cordova prepare&amp;lt;/tt&amp;gt; to work if you are going to go on and build the app.&lt;br /&gt;
&lt;br /&gt;
== Open the app in the browser ==&lt;br /&gt;
First start Chromium via the command line using the custom parameters as is mentioned here: [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
&lt;br /&gt;
and then, start the Ionic server:&lt;br /&gt;
&lt;br /&gt;
 ionic serve --browser chromium         # or chrome or whatever browser.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to open any browser you should run:&lt;br /&gt;
&lt;br /&gt;
 ionic serve -b&lt;br /&gt;
&lt;br /&gt;
Congratulations! Now that you have got to the piont where the &#039;ionic serve&#039; command works, you can start doing development on the app. You only need to read the rest of the page if you want to build packaged versions of the app.&lt;br /&gt;
&lt;br /&gt;
== Updating ionic and cordova ==&lt;br /&gt;
&lt;br /&gt;
 sudo npm update -g cordova&lt;br /&gt;
 sudo npm update -g ionic&lt;br /&gt;
&lt;br /&gt;
Update project platforms:&lt;br /&gt;
&lt;br /&gt;
 ionic cordova platform remove android&lt;br /&gt;
 ionic cordova platform remove ios&lt;br /&gt;
 ionic cordova platform add android&lt;br /&gt;
 ionic cordova platform add ios&lt;br /&gt;
&lt;br /&gt;
== Updating plugins ==&lt;br /&gt;
&lt;br /&gt;
 cordova plugin remove your_plugin_id&lt;br /&gt;
 cordova plugin add your_plugin_id&lt;br /&gt;
&lt;br /&gt;
== Building for Android and iOS ==&lt;br /&gt;
&lt;br /&gt;
Please see the guides below to be able to build for Android and iOS using the command line:&lt;br /&gt;
&lt;br /&gt;
Android: https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html&lt;br /&gt;
&lt;br /&gt;
iOS: https://cordova.apache.org/docs/en/latest/guide/platforms/ios/index.html&lt;br /&gt;
&lt;br /&gt;
If the build fails, please run &amp;lt;code&amp;gt;cordova requirements&amp;lt;/code&amp;gt; to check that you fulfilled all requirements for the platform.&lt;br /&gt;
&lt;br /&gt;
If you get errors while building, please see the Troubleshooting section below.&lt;br /&gt;
&lt;br /&gt;
If using &#039;&#039;&#039;Ubuntu&#039;&#039;&#039; you should install the packages: gradle and libgradle-android-plugin-java (and all its dependencies) to build.&lt;br /&gt;
&lt;br /&gt;
== Compiling using AOT ==&lt;br /&gt;
&lt;br /&gt;
Angular has 2 ways of compiling: JIT and AOT. Running &amp;quot;ionic serve&amp;quot; or &amp;quot;ionic build&amp;quot; compiles using JIT by default, which is faster to compile but the app takes longer to start.&lt;br /&gt;
&lt;br /&gt;
When building for release you should always compile using AOT, otherwise the app can take too long to start in some devices. The default AOT compiling causes some issues with the database activity and the [[Mobile support for plugins]], so you have to modify a couple of files in order to make this work.&lt;br /&gt;
&lt;br /&gt;
First you need to open the file: &#039;&#039;node_modules/@angular/platform-browser-dynamic/esm5/platform-browser-dynamic.js&#039;&#039;. Search the variable called &amp;quot;&#039;&#039;_NO_RESOURCE_LOADER&#039;&#039;&amp;quot;, you&#039;ll see it has a function named &amp;quot;&#039;&#039;get&#039;&#039;&amp;quot; with this line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
throw new Error(&amp;quot;No ResourceLoader implementation has been provided. Can&#039;t read the url \&amp;quot;&amp;quot; + url + &amp;quot;\&amp;quot;&amp;quot;);&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remove that line and put this code instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
        url = &#039;templates/&#039; + url;&lt;br /&gt;
&lt;br /&gt;
        var resolve;&lt;br /&gt;
        var reject;&lt;br /&gt;
        var promise = new Promise(function (res, rej) {&lt;br /&gt;
            resolve = res;&lt;br /&gt;
            reject = rej;&lt;br /&gt;
        });&lt;br /&gt;
        var xhr = new XMLHttpRequest();&lt;br /&gt;
        xhr.open(&#039;GET&#039;, url, true);&lt;br /&gt;
        xhr.responseType = &#039;text&#039;;&lt;br /&gt;
        xhr.onload = function () {&lt;br /&gt;
            // responseText is the old-school way of retrieving response (supported by IE8 &amp;amp; 9)&lt;br /&gt;
            // response/responseType properties were introduced in ResourceLoader Level2 spec (supported by IE10)&lt;br /&gt;
            var response = xhr.response || xhr.responseText;&lt;br /&gt;
            // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)&lt;br /&gt;
            var status = xhr.status === 1223 ? 204 : xhr.status;&lt;br /&gt;
            // fix status code when it is 0 (0 status is undocumented).&lt;br /&gt;
            // Occurs when accessing file resources or on Android 4.1 stock browser&lt;br /&gt;
            // while retrieving files from application cache.&lt;br /&gt;
            if (status === 0) {&lt;br /&gt;
                status = response ? 200 : 0;&lt;br /&gt;
            }&lt;br /&gt;
            if (200 &amp;lt;= status &amp;amp;&amp;amp; status &amp;lt;= 300) {&lt;br /&gt;
                resolve(response);&lt;br /&gt;
            }&lt;br /&gt;
            else {&lt;br /&gt;
                reject(&amp;quot;Failed to load &amp;quot; + url);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        xhr.onerror = function () { reject(&amp;quot;Failed to load &amp;quot; + url); };&lt;br /&gt;
        xhr.send();&lt;br /&gt;
        return promise;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We tried to replace the default loader with our own implementation, but we weren&#039;t able to make the compiler work so the only solution left was to modify the default one.&lt;br /&gt;
&lt;br /&gt;
Now you need to open the file: &#039;&#039;node_modules/@ionic/app-scripts/dist/util/config.js&#039;&#039;. In that file you need to remove the &#039;&#039;context.isProd&#039;&#039; condition from the options &#039;&#039;runMinifyJs&#039;&#039; and &#039;&#039;optimizeJs&#039;&#039;. So the final code for that part should be like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    context.runMinifyJs = [&lt;br /&gt;
        context.runMinifyJs,&lt;br /&gt;
        hasArg(&#039;--minifyJs&#039;)&lt;br /&gt;
    ].find(function (val) { return typeof val === &#039;boolean&#039;; });&lt;br /&gt;
    context.runMinifyCss = [&lt;br /&gt;
        context.runMinifyCss,&lt;br /&gt;
        context.isProd || hasArg(&#039;--minifyCss&#039;)&lt;br /&gt;
    ].find(function (val) { return typeof val === &#039;boolean&#039;; });&lt;br /&gt;
    context.optimizeJs = [&lt;br /&gt;
        context.optimizeJs,&lt;br /&gt;
        hasArg(&#039;--optimizeJs&#039;)&lt;br /&gt;
    ].find(function (val) { return typeof val === &#039;boolean&#039;; });&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We want to compile in production mode but without optimizing and minifying Javascript because that breaks our plugins support. However, Ionic doesn&#039;t let you do that, so the only option is to do this change.&lt;br /&gt;
&lt;br /&gt;
With these changes done you can now compile using production mode:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
npm run ionic:build -- --prod&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This command will generate the app files and put them inside &#039;&#039;www&#039;&#039; folder. If you now want to install that app in a real device you can run &amp;quot;&#039;&#039;cordova run android&#039;&#039;&amp;quot; or &amp;quot;&#039;&#039;cordova build ios&#039;&#039;&amp;quot; (please don&#039;t use &amp;quot;&#039;&#039;ionic cordova ...&#039;&#039;&amp;quot; or &amp;quot;&#039;&#039;ionic serve&#039;&#039;&amp;quot; because it will override your build files!).&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Strange NPM errors ===&lt;br /&gt;
&lt;br /&gt;
To get more debug output from npm commands, see https://docs.npmjs.com/misc/config#shorthands-and-other-cli-niceties. In particular try adding &amp;lt;tt&amp;gt;--loglevel verbose&amp;lt;/tt&amp;gt; (or &amp;lt;tt&amp;gt;--loglevel info&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--loglevel silly&amp;lt;/tt&amp;gt;) to the command-line.&lt;br /&gt;
&lt;br /&gt;
=== Error: libsass bindings not found. Try reinstalling node-sass? ===&lt;br /&gt;
&lt;br /&gt;
Please read: http://fettblog.eu/gulp-and-node4-first-aid/, alternatively you must be sure that you installed Node v0.12&lt;br /&gt;
&lt;br /&gt;
=== node-gyp\src\win_delay_load_hook.c(34): error C2373: &#039;__pfnDliNotifyHook2&#039;: redefinition; different type modifiers ===&lt;br /&gt;
&lt;br /&gt;
Try updating npm to the latest version using: &lt;br /&gt;
&lt;br /&gt;
  npm install -g npm@latest&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== com.android.dex.DexException: Multiple dex files define XXX ===&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations {&lt;br /&gt;
      all*.exclude group: &#039;com.android.support&#039;, module: &#039;support-v4&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
=== Could not resolve all dependencies for configuration &#039;:_debugCompile&#039;. ===&lt;br /&gt;
Open the Android SDK Manager and make sure you have installed: Android Support Repository, Android Support Library, Google Play Services and Google Repository.&lt;br /&gt;
&lt;br /&gt;
=== Could not find com.android.support:support-v4:XXX ===&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations.all {&lt;br /&gt;
      resolutionStrategy.force &#039;com.android.support:support-v4:24.0.0&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
=== ERROR: In &amp;lt;declare-styleable&amp;gt; FontFamilyFont, unable to find attribute android:font  ===&lt;br /&gt;
&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
 android {&lt;br /&gt;
     compileSdkVersion 26&lt;br /&gt;
     buildToolsVersion &amp;quot;26.0.1&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Error: Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.  ===&lt;br /&gt;
&lt;br /&gt;
1. Download Android studio - https://developer.android.com/studio/&lt;br /&gt;
&lt;br /&gt;
2. Copy the folder android-studio/plugins/android/lib/templates&lt;br /&gt;
&lt;br /&gt;
3. Paste in the folder android-sdk-folder/Sdk/tools&lt;br /&gt;
&lt;br /&gt;
=== Could not find com.android.support:support-v4:27.1.0 ===&lt;br /&gt;
&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and configure like this:&lt;br /&gt;
&lt;br /&gt;
  allprojects {&lt;br /&gt;
      repositories {&lt;br /&gt;
          jcenter()&lt;br /&gt;
          maven {&lt;br /&gt;
              url &amp;quot;https://maven.google.com&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
      }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
=== Error: not found: make ===&lt;br /&gt;
&lt;br /&gt;
If you see this error in Ubuntu, run &amp;lt;tt&amp;gt;sudo apt-get install build-essential&amp;lt;/tt&amp;gt; and retry.&lt;br /&gt;
&lt;br /&gt;
=== Current working directory is not a Cordova-based project. ===&lt;br /&gt;
&lt;br /&gt;
If you see this error during &amp;lt;tt&amp;gt;npm setup&amp;lt;/tt&amp;gt;, run &amp;lt;tt&amp;gt;mkdir www&amp;lt;/tt&amp;gt; and retry.&lt;br /&gt;
&lt;br /&gt;
=== ReferenceError: internalBinding is not defined ===&lt;br /&gt;
&lt;br /&gt;
This [https://stackoverflow.com/questions/53146394/node-app-fails-to-run-on-mojave-referenceerror-internalbinding-is-not-defined seems to be] an error with &#039;natives&#039; prior to 1.1.6. I fixed it using &amp;lt;tt&amp;gt;npm install natives@1.1.6&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== ReferenceError: internalBinding is not defined ===&lt;br /&gt;
&lt;br /&gt;
This [https://stackoverflow.com/questions/53146394/node-app-fails-to-run-on-mojave-referenceerror-internalbinding-is-not-defined seems to be] an error with &#039;natives&#039; prior to 1.1.6. I fixed it using &amp;lt;tt&amp;gt;npm install natives@1.1.6&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== npm update check failed===&lt;br /&gt;
&lt;br /&gt;
I got the error&lt;br /&gt;
&lt;br /&gt;
 │                   npm update check failed                   │&lt;br /&gt;
 │             Try running with sudo or get access             │ &lt;br /&gt;
 │            to the local update config store via             │&lt;br /&gt;
 │ sudo chown -R $USER:$(id -gn $USER) C:\Users\username\.config │&lt;br /&gt;
&lt;br /&gt;
on Windows because I installed too much as admin, and the suggested command does not work on Windows. The is to manually check the ownership of all the files in C:\Users\username\.config\configstore. In my case it was update-notifier-npm.json which got changed to be owned by Administrator.&lt;br /&gt;
&lt;br /&gt;
===Unhandled rejection Error: Command failed: C:\cygwin64\bin\git.EXE ...===&lt;br /&gt;
&lt;br /&gt;
You need to heed the advice at https://www.npmjs.com/package/npm#installing-on-cygwin. Cygwin users are not welcome in the Node world. However, you just need to ensure that Msysgit is on your windows path and that the cygwin bin folder is not. Then always use another shell like Powershell for your Moodle mobile development. (You don&#039;t need your Cygwin bin folder on the Windows path. It automatically gets added to the path when you lauch Cygwin bash.)&lt;br /&gt;
&lt;br /&gt;
===The product name change (&amp;lt;name&amp;gt; tag) in config.xml is not supported dynamically===&lt;br /&gt;
&lt;br /&gt;
According to Google, this happens when you create the iOS platform with a certain &amp;lt;name&amp;gt; and then you change that name in config.xml. It&#039;s weird that the installation process does that, it should create the platform with the right name unless it was changed manually.&lt;br /&gt;
&lt;br /&gt;
The solution seems to be removing and adding the iOS platform again:&lt;br /&gt;
&lt;br /&gt;
 ionic platform remove ios&lt;br /&gt;
 ionic platform add ios&lt;br /&gt;
&lt;br /&gt;
Note that this does not seem to prevent &amp;lt;tt&amp;gt;ionic --serve&amp;lt;/tt&amp;gt; from serving a working app if you run gulp after &amp;lt;tt&amp;gt;npm run setup&amp;lt;/tt&amp;gt; has failed with this error.&lt;br /&gt;
&lt;br /&gt;
===Failed to install &#039;cordova-plugin-file-transfer&#039;: CordovaError: Version of installed plugin: &amp;quot;cordova-plugin-file@4.3.3&amp;quot; does not satisfy dependency plugin requirement &amp;quot;cordova-plugin-file@&amp;gt;=5.0.0&amp;quot;.===&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;cordova-plugin-file&#039;&#039; version specified in config.xml is 6.0.1, for some reason the installation process installed a wrong version for that plugin. You can manually install the &#039;&#039;cordova-plugin-file&#039;&#039; plugin like this:&lt;br /&gt;
&lt;br /&gt;
 cordova plugin remove cordova-plugin-file&lt;br /&gt;
 cordova plugin add cordova-plugin-file@6.0.1&lt;br /&gt;
&lt;br /&gt;
Please notice that if there is any plugin installed that depends on &#039;&#039;cordova-plugin-file&#039;&#039; you&#039;ll have to remove and re-add them too.&lt;br /&gt;
&lt;br /&gt;
Note that this does not seem to prevent &amp;lt;tt&amp;gt;ionic --serve&amp;lt;/tt&amp;gt; from serving a working app if you run gulp after &amp;lt;tt&amp;gt;npm run setup&amp;lt;/tt&amp;gt; has failed with this error.&lt;br /&gt;
&lt;br /&gt;
===Cannot download &amp;quot;https://github.com/sass/node-sass/releases/download/v4.11.0/linux-x64-72_binding.node&amp;quot;===&lt;br /&gt;
Force using node 11, since node-sass for node 12 is not yet avalaible. See [https://tracker.moodle.org/browse/MOBILE-2999|MOBILE-2999] for more info.&lt;br /&gt;
&lt;br /&gt;
===Mac: linker code failed with exit code 1===&lt;br /&gt;
If you get this error when trying to build the Moodle app with XCode, some dependencies might not have installed correctly.&lt;br /&gt;
&lt;br /&gt;
Ensure you have followed the [https://docs.moodle.org/dev/Setting_up_your_development_environment_for_Moodle_Mobile_2#Mac_only:_Push_notifications| Mac only: Push notifications] steps above (particularly opening the .xcworkspace file rather than the .xcodeproj file). Then run the following:&lt;br /&gt;
&lt;br /&gt;
  npm run setup&lt;br /&gt;
  cd platforms/ios&lt;br /&gt;
  pod install&lt;br /&gt;
&lt;br /&gt;
Now try running the build again in XCode.&lt;br /&gt;
&lt;br /&gt;
===Windows: &amp;lt;code&amp;gt;npm run ionic:serve&amp;lt;/code&amp;gt; hangs after &amp;quot;Starting &#039;watch&#039;&amp;quot;===&lt;br /&gt;
This appears to have happened since the move to npx. Try running the npx commands generated by npm run directly:&lt;br /&gt;
  npx gulp watch &amp;amp; npx ionic-app-scripts serve -b --devapp --address=0.0.0.0&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
http://ionicframework.com/docs/cli/&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=56153</id>
		<title>Setting up your development environment for the Moodle App</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=56153"/>
		<updated>2019-06-11T14:28:16Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Add troubleshooting guide for XCode build error&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile}}&lt;br /&gt;
{{Moodle Mobile 3.5}}&lt;br /&gt;
&lt;br /&gt;
Note: These instructions do work (give or take) for the current 3.5.x version of the Moodle Mobile app.&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The structure of this page is&lt;br /&gt;
* the first part, up to the point where you get the &#039;ionic serve&#039; command to work is what you need to be able to do development on the app and test it in a browser.&lt;br /&gt;
* the second part is about how to buld a version of the app that can be run on a device.&lt;br /&gt;
* then at the end is a list of troubleshooting advice. If you encouter a problem that is not already listed, please consider adding it.&lt;br /&gt;
&lt;br /&gt;
The majority of your development will be done using a browser. You will probably on begin to use an emulator once you need to simulate a real mobile device.&lt;br /&gt;
&lt;br /&gt;
If you are just [[Mobile support for plugins|adding mobile support to plugins]], remember that most of your development can be done using the online pre-built version at https://mobileapp.moodledemo.net/ (with Chrome or Chromium). However, if you want to be able to to write [[Acceptance_testing_for_the_mobile_app|automated acceptance tests for the app]] then you need to follow this page at least as far as getting the ionic serve command to work on this page.&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
&lt;br /&gt;
Windows tip: ingore any use of the sudo command below. Just use the command without it. Most things work that way, and if they don&#039;t try in a Powershell window that you have opened with &#039;Run as administrator ...&#039;.&lt;br /&gt;
&lt;br /&gt;
===Install a browser for development===&lt;br /&gt;
&lt;br /&gt;
We recommend Chromium browser (Google Chrome open source version) https://download-chromium.appspot.com/&lt;br /&gt;
Please, read [[Moodle_Mobile_development_using_Chrome_or_Chromium]] for more information&lt;br /&gt;
&lt;br /&gt;
===Install git===&lt;br /&gt;
&lt;br /&gt;
https://git-scm.com/book/en/v2/Getting-Started-Installing-Git&lt;br /&gt;
&lt;br /&gt;
===Install Node.js===&lt;br /&gt;
&lt;br /&gt;
On Linux we recommend you use [https://github.com/creationix/nvm nvm] - this lets you switch Node versions, and makes the install a bit easier than the official installation route.&lt;br /&gt;
&lt;br /&gt;
 nvm install latest&lt;br /&gt;
 nvm use 11.12.0 # Or whatever number nvm install reported.&lt;br /&gt;
&lt;br /&gt;
(Exact version is probably not critical. Moodle HQ devs report using both 8.12.x and 11.12.0 as of 2019-03-25.)&lt;br /&gt;
&lt;br /&gt;
On Windows we recommend you use https://github.com/coreybutler/nvm-windows. Same nvm commands as for Linux.&lt;br /&gt;
&lt;br /&gt;
On Mac users we recommend to install NodeJS via Macports.&lt;br /&gt;
&lt;br /&gt;
(It may seem simpler and easier to install directly from http://nodejs.org, but actually it seems to me more tricky to get that to work. If you have previously installed Node directly, and want to switch to nvm, you need to un-install node completely before installing nvm - or Google for trouble-shooting instructions, for example https://github.com/coreybutler/nvm-windows/issues/58#issuecomment-272608696.)&lt;br /&gt;
&lt;br /&gt;
===Install ionic===&lt;br /&gt;
 npm cache clean&lt;br /&gt;
 npm install -g cordova@8.1.2 ionic    # (If it throws an EACCESS error, run it again with sudo)&lt;br /&gt;
&lt;br /&gt;
===Install the npm required packages===&lt;br /&gt;
 sudo npm install -g gulp                      # (This will install gulp in a folder that should be in the PATH)&lt;br /&gt;
&lt;br /&gt;
===Windows only: Native build dependencies===&lt;br /&gt;
&lt;br /&gt;
node-gyp requires native build tools for your platform.  If you&#039;re developing on Mac or Linux, you&#039;ll probably have these already ([https://github.com/nodejs/node-gyp/blob/master/README.md refer to the docs if not]), on Windows, run the following command as administrator (in cmd or Powershell):&lt;br /&gt;
&lt;br /&gt;
 npm install --global --production windows-build-tools&lt;br /&gt;
&lt;br /&gt;
Warning! this installer can take a very, very long time to run. We were seeing it take hours. Literally. Be prepared to be very patient. Don&#039;t just make the natural assumption that it has crashed.&lt;br /&gt;
&lt;br /&gt;
===Mac only: Push notifications===&lt;br /&gt;
&lt;br /&gt;
Phonegap plugin push 1.9.0 requires CocoaPods to work on a Mac. The installation steps can be found in https://cocoapods.org/&lt;br /&gt;
&lt;br /&gt;
 sudo gem install cocoapods&lt;br /&gt;
 pod setup&lt;br /&gt;
&lt;br /&gt;
Please note that for compiling the app in Mac you need to open the .xcworkspace file, more information here: MOBILE-1970&lt;br /&gt;
&lt;br /&gt;
== Clone the app base code ==&lt;br /&gt;
&lt;br /&gt;
Clone the code base into a local directory in your computer.&lt;br /&gt;
It may be an idea to work from the integration branch rather than master.&lt;br /&gt;
 git clone https://github.com/moodlehq/moodlemobile2.git moodlemobiledirectory&lt;br /&gt;
 cd moodlemobiledirectory&lt;br /&gt;
 git checkout integration&lt;br /&gt;
&lt;br /&gt;
== Setup the environment ==&lt;br /&gt;
&lt;br /&gt;
Please, note that if you are creating a custom app with a custom URL scheme, you should edit the /package.json and /config.xml files and specify there your custom URL_SCHEME (replacing the existing value) and your [https://github.com/phonegap/phonegap-plugin-push/blob/master/docs/INSTALLATION.md GCMPN SENDER_ID].&lt;br /&gt;
&lt;br /&gt;
The following command must be run in the project&#039;s root folder:&lt;br /&gt;
 npm run setup&lt;br /&gt;
&lt;br /&gt;
If this fails, you can see what it is doing by looking at the &#039;scripts&#039; section in package.json. At the moment it is doing &amp;lt;tt&amp;gt;npm install &amp;amp;&amp;amp; cordova prepare &amp;amp;&amp;amp; gulp&amp;lt;/tt&amp;gt;. That is, running three commands back-to-back, but only carrying on if the previous one succeeds completely. You can try running the three commands separately. If you do, &amp;lt;tt&amp;gt;ionic serve&amp;lt;/tt&amp;gt; (see below) may work, even if &amp;lt;tt&amp;gt;cordova prepare&amp;lt;/tt&amp;gt; gives errors. You only really need &amp;lt;tt&amp;gt;cordova prepare&amp;lt;/tt&amp;gt; to work if you are going to go on and build the app.&lt;br /&gt;
&lt;br /&gt;
== Open the app in the browser ==&lt;br /&gt;
First start Chromium via the command line using the custom parameters as is mentioned here: [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
&lt;br /&gt;
and then, start the Ionic server:&lt;br /&gt;
&lt;br /&gt;
 ionic serve --browser chromium         # or chrome or whatever browser.&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to open any browser you should run:&lt;br /&gt;
&lt;br /&gt;
 ionic serve -b&lt;br /&gt;
&lt;br /&gt;
Congratulations! Now that you have got to the piont where the &#039;ionic serve&#039; command works, you can start doing development on the app. You only need to read the rest of the page if you want to build packaged versions of the app.&lt;br /&gt;
&lt;br /&gt;
== Updating ionic and cordova ==&lt;br /&gt;
&lt;br /&gt;
 sudo npm update -g cordova&lt;br /&gt;
 sudo npm update -g ionic&lt;br /&gt;
&lt;br /&gt;
Update project platforms:&lt;br /&gt;
&lt;br /&gt;
 ionic cordova platform remove android&lt;br /&gt;
 ionic cordova platform remove ios&lt;br /&gt;
 ionic cordova platform add android&lt;br /&gt;
 ionic cordova platform add ios&lt;br /&gt;
&lt;br /&gt;
== Updating plugins ==&lt;br /&gt;
&lt;br /&gt;
 cordova plugin remove your_plugin_id&lt;br /&gt;
 cordova plugin add your_plugin_id&lt;br /&gt;
&lt;br /&gt;
== Building for Android and iOS ==&lt;br /&gt;
&lt;br /&gt;
Please see the guides below to be able to build for Android and iOS using the command line:&lt;br /&gt;
&lt;br /&gt;
Android: https://cordova.apache.org/docs/en/latest/guide/platforms/android/index.html&lt;br /&gt;
&lt;br /&gt;
iOS: https://cordova.apache.org/docs/en/latest/guide/platforms/ios/index.html&lt;br /&gt;
&lt;br /&gt;
If the build fails, please run &amp;lt;code&amp;gt;cordova requirements&amp;lt;/code&amp;gt; to check that you fulfilled all requirements for the platform.&lt;br /&gt;
&lt;br /&gt;
If you get errors while building, please see the Troubleshooting section below.&lt;br /&gt;
&lt;br /&gt;
If using &#039;&#039;&#039;Ubuntu&#039;&#039;&#039; you should install the packages: gradle and libgradle-android-plugin-java (and all its dependencies) to build.&lt;br /&gt;
&lt;br /&gt;
== Compiling using AOT ==&lt;br /&gt;
&lt;br /&gt;
Angular has 2 ways of compiling: JIT and AOT. Running &amp;quot;ionic serve&amp;quot; or &amp;quot;ionic build&amp;quot; compiles using JIT by default, which is faster to compile but the app takes longer to start.&lt;br /&gt;
&lt;br /&gt;
When building for release you should always compile using AOT, otherwise the app can take too long to start in some devices. The default AOT compiling causes some issues with the database activity and the [[Mobile support for plugins]], so you have to modify a couple of files in order to make this work.&lt;br /&gt;
&lt;br /&gt;
First you need to open the file: &#039;&#039;node_modules/@angular/platform-browser-dynamic/esm5/platform-browser-dynamic.js&#039;&#039;. Search the variable called &amp;quot;&#039;&#039;_NO_RESOURCE_LOADER&#039;&#039;&amp;quot;, you&#039;ll see it has a function named &amp;quot;&#039;&#039;get&#039;&#039;&amp;quot; with this line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
throw new Error(&amp;quot;No ResourceLoader implementation has been provided. Can&#039;t read the url \&amp;quot;&amp;quot; + url + &amp;quot;\&amp;quot;&amp;quot;);&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remove that line and put this code instead:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
        url = &#039;templates/&#039; + url;&lt;br /&gt;
&lt;br /&gt;
        var resolve;&lt;br /&gt;
        var reject;&lt;br /&gt;
        var promise = new Promise(function (res, rej) {&lt;br /&gt;
            resolve = res;&lt;br /&gt;
            reject = rej;&lt;br /&gt;
        });&lt;br /&gt;
        var xhr = new XMLHttpRequest();&lt;br /&gt;
        xhr.open(&#039;GET&#039;, url, true);&lt;br /&gt;
        xhr.responseType = &#039;text&#039;;&lt;br /&gt;
        xhr.onload = function () {&lt;br /&gt;
            // responseText is the old-school way of retrieving response (supported by IE8 &amp;amp; 9)&lt;br /&gt;
            // response/responseType properties were introduced in ResourceLoader Level2 spec (supported by IE10)&lt;br /&gt;
            var response = xhr.response || xhr.responseText;&lt;br /&gt;
            // normalize IE9 bug (http://bugs.jquery.com/ticket/1450)&lt;br /&gt;
            var status = xhr.status === 1223 ? 204 : xhr.status;&lt;br /&gt;
            // fix status code when it is 0 (0 status is undocumented).&lt;br /&gt;
            // Occurs when accessing file resources or on Android 4.1 stock browser&lt;br /&gt;
            // while retrieving files from application cache.&lt;br /&gt;
            if (status === 0) {&lt;br /&gt;
                status = response ? 200 : 0;&lt;br /&gt;
            }&lt;br /&gt;
            if (200 &amp;lt;= status &amp;amp;&amp;amp; status &amp;lt;= 300) {&lt;br /&gt;
                resolve(response);&lt;br /&gt;
            }&lt;br /&gt;
            else {&lt;br /&gt;
                reject(&amp;quot;Failed to load &amp;quot; + url);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        xhr.onerror = function () { reject(&amp;quot;Failed to load &amp;quot; + url); };&lt;br /&gt;
        xhr.send();&lt;br /&gt;
        return promise;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We tried to replace the default loader with our own implementation, but we weren&#039;t able to make the compiler work so the only solution left was to modify the default one.&lt;br /&gt;
&lt;br /&gt;
Now you need to open the file: &#039;&#039;node_modules/@ionic/app-scripts/dist/util/config.js&#039;&#039;. In that file you need to remove the &#039;&#039;context.isProd&#039;&#039; condition from the options &#039;&#039;runMinifyJs&#039;&#039; and &#039;&#039;optimizeJs&#039;&#039;. So the final code for that part should be like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    context.runMinifyJs = [&lt;br /&gt;
        context.runMinifyJs,&lt;br /&gt;
        hasArg(&#039;--minifyJs&#039;)&lt;br /&gt;
    ].find(function (val) { return typeof val === &#039;boolean&#039;; });&lt;br /&gt;
    context.runMinifyCss = [&lt;br /&gt;
        context.runMinifyCss,&lt;br /&gt;
        context.isProd || hasArg(&#039;--minifyCss&#039;)&lt;br /&gt;
    ].find(function (val) { return typeof val === &#039;boolean&#039;; });&lt;br /&gt;
    context.optimizeJs = [&lt;br /&gt;
        context.optimizeJs,&lt;br /&gt;
        hasArg(&#039;--optimizeJs&#039;)&lt;br /&gt;
    ].find(function (val) { return typeof val === &#039;boolean&#039;; });&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We want to compile in production mode but without optimizing and minifying Javascript because that breaks our plugins support. However, Ionic doesn&#039;t let you do that, so the only option is to do this change.&lt;br /&gt;
&lt;br /&gt;
With these changes done you can now compile using production mode:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
npm run ionic:build -- --prod&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This command will generate the app files and put them inside &#039;&#039;www&#039;&#039; folder. If you now want to install that app in a real device you can run &amp;quot;&#039;&#039;cordova run android&#039;&#039;&amp;quot; or &amp;quot;&#039;&#039;cordova build ios&#039;&#039;&amp;quot; (please don&#039;t use &amp;quot;&#039;&#039;ionic cordova ...&#039;&#039;&amp;quot; or &amp;quot;&#039;&#039;ionic serve&#039;&#039;&amp;quot; because it will override your build files!).&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Strange NPM errors ===&lt;br /&gt;
&lt;br /&gt;
To get more debug output from npm commands, see https://docs.npmjs.com/misc/config#shorthands-and-other-cli-niceties. In particular try adding &amp;lt;tt&amp;gt;--loglevel verbose&amp;lt;/tt&amp;gt; (or &amp;lt;tt&amp;gt;--loglevel info&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;--loglevel silly&amp;lt;/tt&amp;gt;) to the command-line.&lt;br /&gt;
&lt;br /&gt;
=== Error: libsass bindings not found. Try reinstalling node-sass? ===&lt;br /&gt;
&lt;br /&gt;
Please read: http://fettblog.eu/gulp-and-node4-first-aid/, alternatively you must be sure that you installed Node v0.12&lt;br /&gt;
&lt;br /&gt;
=== node-gyp\src\win_delay_load_hook.c(34): error C2373: &#039;__pfnDliNotifyHook2&#039;: redefinition; different type modifiers ===&lt;br /&gt;
&lt;br /&gt;
Try updating npm to the latest version using: &lt;br /&gt;
&lt;br /&gt;
  npm install -g npm@latest&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== com.android.dex.DexException: Multiple dex files define XXX ===&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations {&lt;br /&gt;
      all*.exclude group: &#039;com.android.support&#039;, module: &#039;support-v4&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
=== Could not resolve all dependencies for configuration &#039;:_debugCompile&#039;. ===&lt;br /&gt;
Open the Android SDK Manager and make sure you have installed: Android Support Repository, Android Support Library, Google Play Services and Google Repository.&lt;br /&gt;
&lt;br /&gt;
=== Could not find com.android.support:support-v4:XXX ===&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations.all {&lt;br /&gt;
      resolutionStrategy.force &#039;com.android.support:support-v4:24.0.0&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
=== ERROR: In &amp;lt;declare-styleable&amp;gt; FontFamilyFont, unable to find attribute android:font  ===&lt;br /&gt;
&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
 android {&lt;br /&gt;
     compileSdkVersion 26&lt;br /&gt;
     buildToolsVersion &amp;quot;26.0.1&amp;quot;&lt;br /&gt;
 }&lt;br /&gt;
&lt;br /&gt;
=== Error: Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.  ===&lt;br /&gt;
&lt;br /&gt;
1. Download Android studio - https://developer.android.com/studio/&lt;br /&gt;
&lt;br /&gt;
2. Copy the folder android-studio/plugins/android/lib/templates&lt;br /&gt;
&lt;br /&gt;
3. Paste in the folder android-sdk-folder/Sdk/tools&lt;br /&gt;
&lt;br /&gt;
=== Could not find com.android.support:support-v4:27.1.0 ===&lt;br /&gt;
&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and configure like this:&lt;br /&gt;
&lt;br /&gt;
  allprojects {&lt;br /&gt;
      repositories {&lt;br /&gt;
          jcenter()&lt;br /&gt;
          maven {&lt;br /&gt;
              url &amp;quot;https://maven.google.com&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
      }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
=== Error: not found: make ===&lt;br /&gt;
&lt;br /&gt;
If you see this error in Ubuntu, run &amp;lt;tt&amp;gt;sudo apt-get install build-essential&amp;lt;/tt&amp;gt; and retry.&lt;br /&gt;
&lt;br /&gt;
=== Current working directory is not a Cordova-based project. ===&lt;br /&gt;
&lt;br /&gt;
If you see this error during &amp;lt;tt&amp;gt;npm setup&amp;lt;/tt&amp;gt;, run &amp;lt;tt&amp;gt;mkdir www&amp;lt;/tt&amp;gt; and retry.&lt;br /&gt;
&lt;br /&gt;
=== ReferenceError: internalBinding is not defined ===&lt;br /&gt;
&lt;br /&gt;
This [https://stackoverflow.com/questions/53146394/node-app-fails-to-run-on-mojave-referenceerror-internalbinding-is-not-defined seems to be] an error with &#039;natives&#039; prior to 1.1.6. I fixed it using &amp;lt;tt&amp;gt;npm install natives@1.1.6&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== ReferenceError: internalBinding is not defined ===&lt;br /&gt;
&lt;br /&gt;
This [https://stackoverflow.com/questions/53146394/node-app-fails-to-run-on-mojave-referenceerror-internalbinding-is-not-defined seems to be] an error with &#039;natives&#039; prior to 1.1.6. I fixed it using &amp;lt;tt&amp;gt;npm install natives@1.1.6&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== npm update check failed===&lt;br /&gt;
&lt;br /&gt;
I got the error&lt;br /&gt;
&lt;br /&gt;
 │                   npm update check failed                   │&lt;br /&gt;
 │             Try running with sudo or get access             │ &lt;br /&gt;
 │            to the local update config store via             │&lt;br /&gt;
 │ sudo chown -R $USER:$(id -gn $USER) C:\Users\username\.config │&lt;br /&gt;
&lt;br /&gt;
on Windows because I installed too much as admin, and the suggested command does not work on Windows. The is to manually check the ownership of all the files in C:\Users\username\.config\configstore. In my case it was update-notifier-npm.json which got changed to be owned by Administrator.&lt;br /&gt;
&lt;br /&gt;
===Unhandled rejection Error: Command failed: C:\cygwin64\bin\git.EXE ...===&lt;br /&gt;
&lt;br /&gt;
You need to heed the advice at https://www.npmjs.com/package/npm#installing-on-cygwin. Cygwin users are not welcome in the Node world. However, you just need to ensure that Msysgit is on your windows path and that the cygwin bin folder is not. Then always use another shell like Powershell for your Moodle mobile development. (You don&#039;t need your Cygwin bin folder on the Windows path. It automatically gets added to the path when you lauch Cygwin bash.)&lt;br /&gt;
&lt;br /&gt;
===The product name change (&amp;lt;name&amp;gt; tag) in config.xml is not supported dynamically===&lt;br /&gt;
&lt;br /&gt;
According to Google, this happens when you create the iOS platform with a certain &amp;lt;name&amp;gt; and then you change that name in config.xml. It&#039;s weird that the installation process does that, it should create the platform with the right name unless it was changed manually.&lt;br /&gt;
&lt;br /&gt;
The solution seems to be removing and adding the iOS platform again:&lt;br /&gt;
&lt;br /&gt;
 ionic platform remove ios&lt;br /&gt;
 ionic platform add ios&lt;br /&gt;
&lt;br /&gt;
Note that this does not seem to prevent &amp;lt;tt&amp;gt;ionic --serve&amp;lt;/tt&amp;gt; from serving a working app if you run gulp after &amp;lt;tt&amp;gt;npm run setup&amp;lt;/tt&amp;gt; has failed with this error.&lt;br /&gt;
&lt;br /&gt;
===Failed to install &#039;cordova-plugin-file-transfer&#039;: CordovaError: Version of installed plugin: &amp;quot;cordova-plugin-file@4.3.3&amp;quot; does not satisfy dependency plugin requirement &amp;quot;cordova-plugin-file@&amp;gt;=5.0.0&amp;quot;.===&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;cordova-plugin-file&#039;&#039; version specified in config.xml is 6.0.1, for some reason the installation process installed a wrong version for that plugin. You can manually install the &#039;&#039;cordova-plugin-file&#039;&#039; plugin like this:&lt;br /&gt;
&lt;br /&gt;
 cordova plugin remove cordova-plugin-file&lt;br /&gt;
 cordova plugin add cordova-plugin-file@6.0.1&lt;br /&gt;
&lt;br /&gt;
Please notice that if there is any plugin installed that depends on &#039;&#039;cordova-plugin-file&#039;&#039; you&#039;ll have to remove and re-add them too.&lt;br /&gt;
&lt;br /&gt;
Note that this does not seem to prevent &amp;lt;tt&amp;gt;ionic --serve&amp;lt;/tt&amp;gt; from serving a working app if you run gulp after &amp;lt;tt&amp;gt;npm run setup&amp;lt;/tt&amp;gt; has failed with this error.&lt;br /&gt;
&lt;br /&gt;
===Cannot download &amp;quot;https://github.com/sass/node-sass/releases/download/v4.11.0/linux-x64-72_binding.node&amp;quot;===&lt;br /&gt;
Force using node 11, since node-sass for node 12 is not yet avalaible. See [https://tracker.moodle.org/browse/MOBILE-2999|MOBILE-2999] for more info.&lt;br /&gt;
&lt;br /&gt;
===Mac: linker code failed with exit code 1===&lt;br /&gt;
If you get this error when trying to build the Moodle app with XCode, some dependencies might not have installed correctly.&lt;br /&gt;
&lt;br /&gt;
Ensure you have followed the [https://docs.moodle.org/dev/Setting_up_your_development_environment_for_Moodle_Mobile_2#Mac_only:_Push_notifications|Mac only: Push notifications] steps above. Then run the following:&lt;br /&gt;
&lt;br /&gt;
  npm setup&lt;br /&gt;
  cd platforms/ios&lt;br /&gt;
  pod install&lt;br /&gt;
&lt;br /&gt;
Now try running the build again in XCode.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
http://ionicframework.com/docs/cli/&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=56113</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=56113"/>
		<updated>2019-06-04T09:00:54Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* Display the plugin only if certain conditions are met */&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;
==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;
===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. The format is the same as in &#039;&#039;useOtherDataForWS&#039;&#039;. If not supplied, no other data will be added. If supplied but empty (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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;
==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;
&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>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=55934</id>
		<title>Creating mobile course formats</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=55934"/>
		<updated>2019-04-16T12:31:26Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* Example: only display certain sections */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Course format plugins can be supported in the app using the CoureCourseFormatDelegate.&lt;br /&gt;
&lt;br /&gt;
To begin, add a handler for this delegate to your course format&#039;s db/mobile.php file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;format_myformat&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;myformat&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseFormatDelegate&#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 \format_myformat\output\mobile.&lt;br /&gt;
                &#039;styles&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;url&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/course/format/myformat/mobile.css&#039;,&lt;br /&gt;
                    &#039;version&#039; =&amp;gt; 2019041000&lt;br /&gt;
                ],&lt;br /&gt;
                &#039;displaysectionselector&#039; =&amp;gt; true, // Set to false to disable the default section selector.&lt;br /&gt;
                &#039;displayenabledownload&#039; =&amp;gt; true, // Set to false to hide the &amp;quot;Enable download&amp;quot; option in the course context menu.&lt;br /&gt;
                &#039;init&#039; =&amp;gt; &#039;myformat_init&#039;&lt;br /&gt;
            ]&lt;br /&gt;
    ]&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As with other plugins, you then use a function in your plugin&#039;s classes/output/mobile.php to return a template:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Main course page.&lt;br /&gt;
     *&lt;br /&gt;
     * @param array $args Standard mobile web service arguments&lt;br /&gt;
     * @return array&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $CFG;&lt;br /&gt;
&lt;br /&gt;
        $course = get_course($args[&#039;courseid&#039;]);&lt;br /&gt;
        require_login($course);&lt;br /&gt;
        $html = $OUTPUT-&amp;gt;render_from_template(&#039;format_oustudyplan/mobile_course&#039;);&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; $html&lt;br /&gt;
                ]&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
               ...&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then your templates/mobile_course.mustache file will contain the angular template to display your page&lt;br /&gt;
&amp;lt;code html&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;allSectionsComponent&amp;quot; [data]=&amp;quot;data&amp;quot; class=&amp;quot;format-myformat&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let section of sections&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-item-divider color=&amp;quot;light&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;core-format-text [text]=&amp;quot;section.name&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
             &amp;lt;!-- Section download. --&amp;gt;&lt;br /&gt;
             &amp;lt;div *ngIf=&amp;quot;section &amp;amp;&amp;amp; downloadEnabled&amp;quot; class=&amp;quot;core-button-spinner&amp;quot; float-end&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Download button. --&amp;gt;&lt;br /&gt;
                &amp;lt;button *ngIf=&amp;quot;section.showDownload &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.download&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                &amp;lt;/button&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Refresh button. --&amp;gt;&lt;br /&gt;
                &amp;lt;button *ngIf=&amp;quot;section.showRefresh &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.refresh&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-icon name=&amp;quot;refresh&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                &amp;lt;/button&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Download progress. --&amp;gt;&lt;br /&gt;
                &amp;lt;ion-badge class=&amp;quot;core-course-download-section-progress&amp;quot; *ngIf=&amp;quot;section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0 &amp;amp;&amp;amp; section.count &amp;lt; section.total&amp;quot;&amp;gt;{{section.count}} / {{section.total}}&amp;lt;/ion-badge&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Spinner (downloading or calculating status). --&amp;gt;&lt;br /&gt;
                &amp;lt;ion-spinner *ngIf=&amp;quot;(section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0) || section.isCalculating&amp;quot;&amp;gt;&amp;lt;/ion-spinner&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-item-divider&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;ion-item text-wrap *ngIf=&amp;quot;section.summary&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;core-format-text [text]=&amp;quot;section.summary&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;ng-container *ngFor=&amp;quot;let module of section.modules&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;ng-container *ngIf=&amp;quot;module.visibleoncoursepage !== 0&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;core-course-module text-wrap [module]=&amp;quot;module&amp;quot; [courseId]=&amp;quot;course.id&amp;quot; [downloadEnabled]=&amp;quot;downloadEnabled&amp;quot; (completionChanged)=&amp;quot;onCompletionChange($event)&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;/core-course-module&amp;gt;&lt;br /&gt;
            &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
        &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
    &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
You don&#039;t have to use a mustache template, you can just use a static angular template.&lt;br /&gt;
&lt;br /&gt;
This will get you to a stage where you have something similar to the core format plugin - a list of sections with headers, each containing its list of course modules.  From here, you can make customisations to support your course format&#039;s features.&lt;br /&gt;
&lt;br /&gt;
Note that there are a few objects available to your template without you having to do anything:&lt;br /&gt;
* sections - this is an array of all the sections on the course, which includes all of the modules within that course.&lt;br /&gt;
* course - this contains the basic course data&lt;br /&gt;
* downloadEnabled - This is set using the option int he context menu, if &amp;quot;displayenabledownload&amp;quot; is used in your db.php&lt;br /&gt;
&lt;br /&gt;
== Example: only display certain sections ==&lt;br /&gt;
When your course page loads, the sections array contains all of the sections on the course.  However, you might not want to display all of these sections on one page.&lt;br /&gt;
You can achieve this by returning the list of sections to display along with the template in classes/output/mobile.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class mobile {&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
       ...&lt;br /&gt;
       $displaysections = myformat\helper::get_list_of_section_ids($courseid);&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; $html&lt;br /&gt;
                ]&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
               &#039;displaysections&#039; =&amp;gt; json_encode($displaysections);&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then filter the list of sections in your template:&lt;br /&gt;
&amp;lt;code html&amp;gt;&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let section of sections&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ng-container *ngIf=&amp;quot;CONTENT_OTHERDATA.displaysections.hasOwnProperty(section.id.toString())&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- code to display the section goes here --&amp;gt;&lt;br /&gt;
        &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
    &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=55933</id>
		<title>Creating mobile course formats</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=55933"/>
		<updated>2019-04-16T11:05:00Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Add example of filtering sections&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Course format plugins can be supported in the app using the CoureCourseFormatDelegate.&lt;br /&gt;
&lt;br /&gt;
To begin, add a handler for this delegate to your course format&#039;s db/mobile.php file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;format_myformat&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;myformat&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseFormatDelegate&#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 \format_myformat\output\mobile.&lt;br /&gt;
                &#039;styles&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;url&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/course/format/myformat/mobile.css&#039;,&lt;br /&gt;
                    &#039;version&#039; =&amp;gt; 2019041000&lt;br /&gt;
                ],&lt;br /&gt;
                &#039;displaysectionselector&#039; =&amp;gt; true, // Set to false to disable the default section selector.&lt;br /&gt;
                &#039;displayenabledownload&#039; =&amp;gt; true, // Set to false to hide the &amp;quot;Enable download&amp;quot; option in the course context menu.&lt;br /&gt;
                &#039;init&#039; =&amp;gt; &#039;myformat_init&#039;&lt;br /&gt;
            ]&lt;br /&gt;
    ]&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As with other plugins, you then use a function in your plugin&#039;s classes/output/mobile.php to return a template:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Main course page.&lt;br /&gt;
     *&lt;br /&gt;
     * @param array $args Standard mobile web service arguments&lt;br /&gt;
     * @return array&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $CFG;&lt;br /&gt;
&lt;br /&gt;
        $course = get_course($args[&#039;courseid&#039;]);&lt;br /&gt;
        require_login($course);&lt;br /&gt;
        $html = $OUTPUT-&amp;gt;render_from_template(&#039;format_oustudyplan/mobile_course&#039;);&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; $html&lt;br /&gt;
                ]&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
               ...&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then your templates/mobile_course.mustache file will contain the angular template to display your page&lt;br /&gt;
&amp;lt;code html&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;allSectionsComponent&amp;quot; [data]=&amp;quot;data&amp;quot; class=&amp;quot;format-myformat&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let section of sections&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-item-divider color=&amp;quot;light&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;core-format-text [text]=&amp;quot;section.name&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
             &amp;lt;!-- Section download. --&amp;gt;&lt;br /&gt;
             &amp;lt;div *ngIf=&amp;quot;section &amp;amp;&amp;amp; downloadEnabled&amp;quot; class=&amp;quot;core-button-spinner&amp;quot; float-end&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Download button. --&amp;gt;&lt;br /&gt;
                &amp;lt;button *ngIf=&amp;quot;section.showDownload &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.download&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                &amp;lt;/button&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Refresh button. --&amp;gt;&lt;br /&gt;
                &amp;lt;button *ngIf=&amp;quot;section.showRefresh &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.refresh&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                    &amp;lt;ion-icon name=&amp;quot;refresh&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
                &amp;lt;/button&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Download progress. --&amp;gt;&lt;br /&gt;
                &amp;lt;ion-badge class=&amp;quot;core-course-download-section-progress&amp;quot; *ngIf=&amp;quot;section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0 &amp;amp;&amp;amp; section.count &amp;lt; section.total&amp;quot;&amp;gt;{{section.count}} / {{section.total}}&amp;lt;/ion-badge&amp;gt;&lt;br /&gt;
                &amp;lt;!-- Spinner (downloading or calculating status). --&amp;gt;&lt;br /&gt;
                &amp;lt;ion-spinner *ngIf=&amp;quot;(section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0) || section.isCalculating&amp;quot;&amp;gt;&amp;lt;/ion-spinner&amp;gt;&lt;br /&gt;
            &amp;lt;/div&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-item-divider&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;ion-item text-wrap *ngIf=&amp;quot;section.summary&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;core-format-text [text]=&amp;quot;section.summary&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
        &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
        &amp;lt;ng-container *ngFor=&amp;quot;let module of section.modules&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;ng-container *ngIf=&amp;quot;module.visibleoncoursepage !== 0&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;core-course-module text-wrap [module]=&amp;quot;module&amp;quot; [courseId]=&amp;quot;course.id&amp;quot; [downloadEnabled]=&amp;quot;downloadEnabled&amp;quot; (completionChanged)=&amp;quot;onCompletionChange($event)&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;/core-course-module&amp;gt;&lt;br /&gt;
            &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
        &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
    &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
You don&#039;t have to use a mustache template, you can just use a static angular template.&lt;br /&gt;
&lt;br /&gt;
This will get you to a stage where you have something similar to the core format plugin - a list of sections with headers, each containing its list of course modules.  From here, you can make customisations to support your course format&#039;s features.&lt;br /&gt;
&lt;br /&gt;
Note that there are a few objects available to your template without you having to do anything:&lt;br /&gt;
* sections - this is an array of all the sections on the course, which includes all of the modules within that course.&lt;br /&gt;
* course - this contains the basic course data&lt;br /&gt;
* downloadEnabled - This is set using the option int he context menu, if &amp;quot;displayenabledownload&amp;quot; is used in your db.php&lt;br /&gt;
&lt;br /&gt;
== Example: only display certain sections ==&lt;br /&gt;
When your course page loads, the sections array contains all of the sections on the course.  However, you might not want to display all of these sections on one page.&lt;br /&gt;
You can achieve this by returning the list of sections to display along with the template in classes/output/mobile.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class mobile {&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
       ...&lt;br /&gt;
       $displaysections = myformat\helper::get_list_of_section_ids($courseid);&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; $html&lt;br /&gt;
                ]&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
               &#039;displaysections&#039; =&amp;gt; json_encode($displaysections);&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then filter the list of sections in your template:&lt;br /&gt;
&amp;lt;code html&amp;gt;&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let section of sections&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ng-container *ngIf=&amp;quot;*ngIf=&amp;quot;CONTENT_OTHERDATA.displaysections.hasOwnProperty(section.id.toString())&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;!-- code to display the section goes here --&amp;gt;&lt;br /&gt;
        &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
    &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=55932</id>
		<title>Creating mobile course formats</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=55932"/>
		<updated>2019-04-16T10:55:08Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Course format plugins can be supported in the app using the CoureCourseFormatDelegate.&lt;br /&gt;
&lt;br /&gt;
To begin, add a handler for this delegate to your course format&#039;s db/mobile.php file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;format_myformat&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;myformat&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseFormatDelegate&#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 \format_myformat\output\mobile.&lt;br /&gt;
                &#039;styles&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;url&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/course/format/myformat/mobile.css&#039;,&lt;br /&gt;
                    &#039;version&#039; =&amp;gt; 2019041000&lt;br /&gt;
                ],&lt;br /&gt;
                &#039;displaysectionselector&#039; =&amp;gt; true, // Set to false to disable the default section selector.&lt;br /&gt;
                &#039;displayenabledownload&#039; =&amp;gt; true, // Set to false to hide the &amp;quot;Enable download&amp;quot; option in the course context menu.&lt;br /&gt;
                &#039;init&#039; =&amp;gt; &#039;myformat_init&#039;&lt;br /&gt;
            ]&lt;br /&gt;
    ]&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As with other plugins, you then use a function in your plugin&#039;s classes/output/mobile.php to return a template:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Main course page.&lt;br /&gt;
     *&lt;br /&gt;
     * @param array $args Standard mobile web service arguments&lt;br /&gt;
     * @return array&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $CFG;&lt;br /&gt;
&lt;br /&gt;
        $course = get_course($args[&#039;courseid&#039;]);&lt;br /&gt;
        require_login($course);&lt;br /&gt;
        $html = $OUTPUT-&amp;gt;render_from_template(&#039;format_oustudyplan/mobile_course&#039;);&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; $html&lt;br /&gt;
                ]&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
               ...&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then your templates/mobile_course.mustache file will contain the angular template to display your page&lt;br /&gt;
&amp;lt;code html&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;allSectionsComponent&amp;quot; [data]=&amp;quot;data&amp;quot; class=&amp;quot;format-myformat&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let section of sections&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-item-divider color=&amp;quot;light&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;core-format-text [text]=&amp;quot;section.name&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
        &amp;lt;!-- Section download. --&amp;gt;&lt;br /&gt;
        &amp;lt;div *ngIf=&amp;quot;section &amp;amp;&amp;amp; downloadEnabled&amp;quot; class=&amp;quot;core-button-spinner&amp;quot; float-end&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Download button. --&amp;gt;&lt;br /&gt;
            &amp;lt;button *ngIf=&amp;quot;section.showDownload &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.download&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Refresh button. --&amp;gt;&lt;br /&gt;
            &amp;lt;button *ngIf=&amp;quot;section.showRefresh &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.refresh&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;ion-icon name=&amp;quot;refresh&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Download progress. --&amp;gt;&lt;br /&gt;
            &amp;lt;ion-badge class=&amp;quot;core-course-download-section-progress&amp;quot; *ngIf=&amp;quot;section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0 &amp;amp;&amp;amp; section.count &amp;lt; section.total&amp;quot;&amp;gt;{{section.count}} / {{section.total}}&amp;lt;/ion-badge&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Spinner (downloading or calculating status). --&amp;gt;&lt;br /&gt;
            &amp;lt;ion-spinner *ngIf=&amp;quot;(section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0) || section.isCalculating&amp;quot;&amp;gt;&amp;lt;/ion-spinner&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item-divider&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-item text-wrap *ngIf=&amp;quot;section.summary&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;core-format-text [text]=&amp;quot;section.summary&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let module of section.modules&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ng-container *ngIf=&amp;quot;module.visibleoncoursepage !== 0&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;core-course-module text-wrap [module]=&amp;quot;module&amp;quot; [courseId]=&amp;quot;course.id&amp;quot; [downloadEnabled]=&amp;quot;downloadEnabled&amp;quot; (completionChanged)=&amp;quot;onCompletionChange($event)&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;/core-course-module&amp;gt;&lt;br /&gt;
        &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
    &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
You don&#039;t have to use a mustache template, you can just use a static angular template.&lt;br /&gt;
&lt;br /&gt;
This will get you to a stage where you have something similar to the core format plugin - a list of sections with headers, each containing its list of course modules.  From here, you can make customisations to support your course format&#039;s features.&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=55931</id>
		<title>Creating mobile course formats</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Creating_mobile_course_formats&amp;diff=55931"/>
		<updated>2019-04-16T10:54:41Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Initial example of a course format plugin.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Course format plugins can be supported in the app using the CoureCourseFormatDelegate.&lt;br /&gt;
&lt;br /&gt;
To begin, add a handler for this delegate to your course format&#039;s db/mobile.php file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$addons = [&lt;br /&gt;
    &#039;format_myformat&#039; =&amp;gt; [&lt;br /&gt;
        &#039;handlers&#039; =&amp;gt; [ // Different places where the plugin will display content.&lt;br /&gt;
            &#039;myformat&#039; =&amp;gt; [ // Handler unique name (alphanumeric).&lt;br /&gt;
                &#039;delegate&#039; =&amp;gt; &#039;CoreCourseFormatDelegate&#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 \format_myformat\output\mobile.&lt;br /&gt;
                &#039;styles&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;url&#039; =&amp;gt; $CFG-&amp;gt;wwwroot . &#039;/course/format/myformat/mobile.css&#039;,&lt;br /&gt;
                    &#039;version&#039; =&amp;gt; 2019041000&lt;br /&gt;
                ],&lt;br /&gt;
                &#039;displaysectionselector&#039; =&amp;gt; true, // Set to false to disable the default section selector.&lt;br /&gt;
                &#039;displayenabledownload&#039; =&amp;gt; true, // Set to false to hide the &amp;quot;Enable download&amp;quot; option in the course context menu.&lt;br /&gt;
                &#039;init&#039; =&amp;gt; &#039;myformat_init&#039;&lt;br /&gt;
            ]&lt;br /&gt;
    ]&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As with other plugins, you then use a function in your plugin&#039;s classes/output/mobile.php to return a template:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class mobile {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * Main course page.&lt;br /&gt;
     *&lt;br /&gt;
     * @param array $args Standard mobile web service arguments&lt;br /&gt;
     * @return array&lt;br /&gt;
     */&lt;br /&gt;
    public static function mobile_course_view($args) {&lt;br /&gt;
        global $OUTPUT, $CFG;&lt;br /&gt;
&lt;br /&gt;
        $course = get_course($args[&#039;courseid&#039;]);&lt;br /&gt;
        require_login($course);&lt;br /&gt;
        $html = $OUTPUT-&amp;gt;render_from_template(&#039;format_oustudyplan/mobile_course&#039;);&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; $html&lt;br /&gt;
                ]&lt;br /&gt;
            ],&lt;br /&gt;
            &#039;otherdata&#039; =&amp;gt; [&lt;br /&gt;
               ...&lt;br /&gt;
            ]&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then your templates/mobile_course.mustache file will contain the angular template to display your page&lt;br /&gt;
&amp;lt;code html&amp;gt;&lt;br /&gt;
{{=&amp;lt;% %&amp;gt;=}}&lt;br /&gt;
&amp;lt;core-dynamic-component [component]=&amp;quot;allSectionsComponent&amp;quot; [data]=&amp;quot;data&amp;quot; class=&amp;quot;format-myformat&amp;quot;&amp;gt;&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let section of sections&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ion-item-divider color=&amp;quot;light&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;core-format-text [text]=&amp;quot;section.name&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
        &amp;lt;!-- Section download. --&amp;gt;&lt;br /&gt;
        &amp;lt;div *ngIf=&amp;quot;section &amp;amp;&amp;amp; downloadEnabled&amp;quot; class=&amp;quot;core-button-spinner&amp;quot; float-end&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Download button. --&amp;gt;&lt;br /&gt;
            &amp;lt;button *ngIf=&amp;quot;section.showDownload &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.download&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;ion-icon name=&amp;quot;cloud-download&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Refresh button. --&amp;gt;&lt;br /&gt;
            &amp;lt;button *ngIf=&amp;quot;section.showRefresh &amp;amp;&amp;amp; !section.isDownloading &amp;amp;&amp;amp; !section.isCalculating&amp;quot; (click)=&amp;quot;prefetch($event, section)&amp;quot; ion-button icon-only clear color=&amp;quot;dark&amp;quot; [attr.aria-label]=&amp;quot;&#039;core.refresh&#039; | translate&amp;quot;&amp;gt;&lt;br /&gt;
                &amp;lt;ion-icon name=&amp;quot;refresh&amp;quot;&amp;gt;&amp;lt;/ion-icon&amp;gt;&lt;br /&gt;
            &amp;lt;/button&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Download progress. --&amp;gt;&lt;br /&gt;
            &amp;lt;ion-badge class=&amp;quot;core-course-download-section-progress&amp;quot; *ngIf=&amp;quot;section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0 &amp;amp;&amp;amp; section.count &amp;lt; section.total&amp;quot;&amp;gt;{{section.count}} / {{section.total}}&amp;lt;/ion-badge&amp;gt;&lt;br /&gt;
            &amp;lt;!-- Spinner (downloading or calculating status). --&amp;gt;&lt;br /&gt;
            &amp;lt;ion-spinner *ngIf=&amp;quot;(section.isDownloading &amp;amp;&amp;amp; section.total &amp;gt; 0) || section.isCalculating&amp;quot;&amp;gt;&amp;lt;/ion-spinner&amp;gt;&lt;br /&gt;
        &amp;lt;/div&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item-divider&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ion-item text-wrap *ngIf=&amp;quot;section.summary&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;core-format-text [text]=&amp;quot;section.summary&amp;quot;&amp;gt;&amp;lt;/core-format-text&amp;gt;&lt;br /&gt;
    &amp;lt;/ion-item&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    &amp;lt;ng-container *ngFor=&amp;quot;let module of section.modules&amp;quot;&amp;gt;&lt;br /&gt;
        &amp;lt;ng-container *ngIf=&amp;quot;module.visibleoncoursepage !== 0&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;core-course-module text-wrap [module]=&amp;quot;module&amp;quot; [courseId]=&amp;quot;course.id&amp;quot; [downloadEnabled]=&amp;quot;downloadEnabled&amp;quot; (completionChanged)=&amp;quot;onCompletionChange($event)&amp;quot;&amp;gt;&lt;br /&gt;
            &amp;lt;/core-course-module&amp;gt;&lt;br /&gt;
        &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
    &amp;lt;/ng-container&amp;gt;&lt;br /&gt;
&amp;lt;/core-dynamic-component&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
You don&#039;t have to use a mustache template, you can just use a static angular template.&lt;br /&gt;
&lt;br /&gt;
This will get you to a stage where you have something similar to the core format plugin - a list of sections with headers, each containing its list of course modules.  From here, you can make customisations to support your course format&#039;s features.&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=55930</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=55930"/>
		<updated>2019-04-16T10:36:52Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* CoreCourseFormatDelegate */&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;
==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;
===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. The format is the same as in &#039;&#039;useOtherDataForWS&#039;&#039;. If not supplied, no other data will be added. If supplied but empty (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 return 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;
==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;
&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>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=55929</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=55929"/>
		<updated>2019-04-16T10:33:26Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* CoreCourseFormatDelegate */&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;
==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.&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;
===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. The format is the same as in &#039;&#039;useOtherDataForWS&#039;&#039;. If not supplied, no other data will be added. If supplied but empty (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 return 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;
==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;
&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>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Templates&amp;diff=55814</id>
		<title>Templates</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Templates&amp;diff=55814"/>
		<updated>2019-03-26T13:54:33Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: &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 simplest case where you have a renderable, templatable object with a class name matching the name of the template that will render it, you do not need to add any renderer code explicity.  Passing your widget directly to $OUTPUT-&amp;gt;renderer() will infer the name of your template, call export_for_template() and render_from_template(), then return the result.&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 your page:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$OUTPUT-&amp;gt;render($widget);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For more complex scenarios, add a render function to your component&#039;s renderer.&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;
        // Do other logic.&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>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Output_renderers&amp;diff=55813</id>
		<title>Output renderers</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Output_renderers&amp;diff=55813"/>
		<updated>2019-03-26T13:53:30Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Update renderer_base::render() documenation to reflect MDL-61869&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = Output renderers&lt;br /&gt;
|state = Implemented&lt;br /&gt;
|tracker = MDL-21235&lt;br /&gt;
|discussion = n/a&lt;br /&gt;
|assignee = [[User:Petr Škoda (škoďák)|Petr Škoda (škoďák)]] + [[User:David Mudrak|David Mudrak]] + feedback and ideas from other developers&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.0}}&lt;br /&gt;
&lt;br /&gt;
== Goals ==&lt;br /&gt;
* stable API&lt;br /&gt;
* easy to use&lt;br /&gt;
* easy to customise via themes&lt;br /&gt;
&lt;br /&gt;
== Renderers ==&lt;br /&gt;
&lt;br /&gt;
Output renderer is a class with collection of methods that handle rendering of visual aspects of Moodle pages, emails, html export, etc. In Moodle 1.9, general output related functions were located in weblib.php and modules stored rendering code in lib.php, locallib.php, view.php, etc. Output renderer instances are obtained through moodle_page::get_renderer($component, $subtype = null, $target = null) method.&lt;br /&gt;
&lt;br /&gt;
The general renderer class naming convention is:&lt;br /&gt;
 {theme name}_{plugin type}_{plugin_name}_{subtype}_renderer_{target}&lt;br /&gt;
&lt;br /&gt;
or (if using namespaces)&lt;br /&gt;
&lt;br /&gt;
 \{plugin type}_{plugin name}\output\{subtype}_renderer_{target} (standard plugin renderer)&lt;br /&gt;
&lt;br /&gt;
 \theme_{theme name}\output\{plugin type}_{plugin name}\{subtype}_renderer_{target} (plugin renderer overridden in a theme)&lt;br /&gt;
&lt;br /&gt;
where theme name prefix, subtype infix and target suffix are optional. Examples of renderer class name are core_renderer, mytheme_core_renderer, mod_workshop_renderer, mod_forum_renderer_cli, \tool_templatelibrary\output\renderer etc.&lt;br /&gt;
&lt;br /&gt;
; Renderer subtype : When get_renderer() is called, $subtype parameter can be provided to obtain a different renderer. Renderer subtypes are used mainly for core subsystems, but it is also possible to use them in modules.&lt;br /&gt;
&lt;br /&gt;
; Renderer targets : In most cases, we are rendering page output for web browsers. Sometimes we need to generate different output for command line scripts, email messages, etc. When get_renderer() is called, $target parameter can be provided to obtain a different renderer. The list of targets will be part of the specification, individual plugins should not invent new rendering targets.&lt;br /&gt;
&lt;br /&gt;
: note that both $subtype and $target works similarly - they just influence the name of the class to be returned by get_renderer(). But there is semantic difference and we will probably have the list of allowed targets limited (defaults to html).&lt;br /&gt;
&lt;br /&gt;
== Overall picture ==&lt;br /&gt;
&lt;br /&gt;
The following UML diagram describes the main classes involved in HTML output rendering and their relationships.&lt;br /&gt;
&lt;br /&gt;
[[Image:uml_output_renderers.png|800px|left]] [[Media:uml_output_renderers.dia|(dia source)]]&lt;br /&gt;
&amp;lt;br clear=&amp;quot;both&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== renderer_base ===&lt;br /&gt;
&lt;br /&gt;
Abstract class every other renderer must extend. Renderer base implements basic methods and support for renderer dispatching.&lt;br /&gt;
&lt;br /&gt;
=== core_renderer ===&lt;br /&gt;
&lt;br /&gt;
Current core_renderer is available through the global $OUTPUT variable. This global should not be used in lower level APIs - only in the main scripts like view.php. When you want to start to print something, you will probably call&lt;br /&gt;
 echo $OUTPUT-&amp;gt;header();&lt;br /&gt;
in your view.php or similar script. To print a page footer and terminate your PHP script, you will call&lt;br /&gt;
 echo $OUTPUT-&amp;gt;footer();&lt;br /&gt;
 die();&lt;br /&gt;
&lt;br /&gt;
Beside these page header and footer related things, core_renderer knows how to display&lt;br /&gt;
# boxes and containers - basically &amp;amp;lt;div&amp;amp;gt; elements&lt;br /&gt;
# general widgets - headings, single buttons, selection forms, arrows in the breadcrumb navigation, language selection popup, user login info etc.&lt;br /&gt;
# error messages, notifications&lt;br /&gt;
# blocks&lt;br /&gt;
&lt;br /&gt;
=== method render() and interface renderable ===&lt;br /&gt;
&lt;br /&gt;
Various widgets (headings, buttons etc.) and other content blocks (submissions, forum posts etc.) are represented as classes. In most cases, they will look like a simple object with just some properties and a constructor to set the most common ones. Core renderer implements protected methods to render these widgets. These widget rendering methods are called render_something() where &#039;&#039;something&#039;&#039; is the name of the widget. Their first parameter must be an instance of &#039;&#039;something&#039;&#039; class that implements interface &#039;&#039;renderable&#039;&#039;. These methods can be overridden by themes.&lt;br /&gt;
&lt;br /&gt;
Public method to render a widget is called render(). It accepts the only parameter which must be an instance of a class implementing &#039;&#039;renderable&#039;&#039; interface. It uses the class name of the widget passed in as parameter to find the corresponding protected rendering method.&lt;br /&gt;
&lt;br /&gt;
If the widget also implements the &amp;quot;templatable&amp;quot; interface, and there is no render_widget helper explcitly defined for this class, then the render method will look for a template name matching the class in the same component. If found, it will automatically render the template with the data exported using $widget-&amp;gt;export_for_template(), no additional renderer code is required.&lt;br /&gt;
&lt;br /&gt;
Implementation of renderer_base::render() (inherited by all renderers)&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class renderer_base {&lt;br /&gt;
    // ...&lt;br /&gt;
   public function render(renderable $widget) {&lt;br /&gt;
        $classparts = explode(&#039;\\&#039;, get_class($widget));&lt;br /&gt;
        // Strip namespaces.&lt;br /&gt;
        $classname = array_pop($classparts);&lt;br /&gt;
        // Remove _renderable suffixes&lt;br /&gt;
        $classname = preg_replace(&#039;/_renderable$/&#039;, &#039;&#039;, $classname);&lt;br /&gt;
&lt;br /&gt;
        $rendermethod = &#039;render_&#039;.$classname;&lt;br /&gt;
        if (method_exists($this, $rendermethod)) {&lt;br /&gt;
            return $this-&amp;gt;$rendermethod($widget);&lt;br /&gt;
        }&lt;br /&gt;
        if ($widget instanceof templatable) {&lt;br /&gt;
            $component = array_shift($classparts);&lt;br /&gt;
            if (!$component) {&lt;br /&gt;
                $component = &#039;core&#039;;&lt;br /&gt;
            }&lt;br /&gt;
            $template = $component . &#039;/&#039; . $classname;&lt;br /&gt;
            $context = $widget-&amp;gt;export_for_template($this);&lt;br /&gt;
            return $this-&amp;gt;render_from_template($template, $context);&lt;br /&gt;
        }&lt;br /&gt;
        throw new coding_exception(&#039;Can not render widget, renderer method (&#039;.$rendermethod.&#039;) not found.&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    // ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For some widgets, a helper method may be implemented to instantiate the widget and render it in the background. Such helper methods have the same name as the widget class and accepts parameters needed to create the widget instance. Themes should not override helper methods because their usage is optional.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Example:&#039;&#039;&#039; User avatar rendering involves following methods and classes:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class core_renderer extends renderer_base {&lt;br /&gt;
    // ...&lt;br /&gt;
    protected function render_user_picture(user_picture $userpicture) {&lt;br /&gt;
        // returns HTML code to be echoed for displaying the user avatar&lt;br /&gt;
    }&lt;br /&gt;
    public function user_picture(stdclass $userrecord, array $options=null) {&lt;br /&gt;
        // helper method that constructs $user_image widget and calls $this-&amp;gt;render()&lt;br /&gt;
        $picture = new user_picture($userrecord, $options);&lt;br /&gt;
        return $this-&amp;gt;render($picture);&lt;br /&gt;
    }&lt;br /&gt;
    // ...&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class user_picture implements renderable {&lt;br /&gt;
    public $userrecord;&lt;br /&gt;
    public $courseid = 0;&lt;br /&gt;
    public $link = true;&lt;br /&gt;
&lt;br /&gt;
    public function __construct(stdclass $userrecord, array $options=null) {&lt;br /&gt;
        // ...&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are two ways a developer can render the user picture. Either using a one-line style:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$user = $DB-&amp;gt;get_record(&#039;user&#039;, array(...));&lt;br /&gt;
echo $OUTPUT-&amp;gt;user_picture($user, array(&#039;courseid&#039; =&amp;gt; $courseid, &#039;link&#039; =&amp;gt; true));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
or an explicit stye:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$user = $DB-&amp;gt;get_record(&#039;user&#039;, array(...));&lt;br /&gt;
$avatar = new user_picture($user);&lt;br /&gt;
$avatar-&amp;gt;courseid = $courseid;&lt;br /&gt;
$avatar-&amp;gt;link = true;&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($avatar);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The point is that the first style is usually one line only, it is also possible to have multiple helpers with different parameters. The second style allows more options without cluttering the simple API with tons of optional method parameters.&lt;br /&gt;
&lt;br /&gt;
Plugins can also use renderable objects and define render_something() functions in plugin renderers.&lt;br /&gt;
&lt;br /&gt;
=== core_renderer_cli ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;_cli&#039;&#039; suffix indicates this is a core renderer for CLI rendering target (CLI scripts). For example headings are using &#039;&#039;&#039;=&amp;gt;&#039;&#039;&#039; instead if H2 html tag.&lt;br /&gt;
&lt;br /&gt;
== Core subsystem renderers ==&lt;br /&gt;
&lt;br /&gt;
== Plugin renderers ==&lt;br /&gt;
&lt;br /&gt;
Plugin renderers are stored in renderer.php file in the root folder of the plugin or in /classes/. They all extend plugin_renderer_base method.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== plugin_renderer_base ===&lt;br /&gt;
&lt;br /&gt;
An important thing about this class is that it keeps a reference to an instance of (typically) core_renderer which is used as an underlying renderer. This reference is kept in protected $output member variable. So, there is no more $OUTPUT in plugin renderers. They all use $this-&amp;gt;output-&amp;gt;something() to render the output.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Example:&#039;&#039;&#039; Workshop module defines mod_workshop_renderer class in mod/workshop/renderer.php file. The renderer defines a method to display a student submission.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// in renderer.php:&lt;br /&gt;
class workshop_submission implements renderable {&lt;br /&gt;
&lt;br /&gt;
    public function __construct(stdclass $submission, $anonymous = false, array $attachments = null) {&lt;br /&gt;
        // here the widget is prepared and all necessary logic is performed&lt;br /&gt;
        if ($anonymous) {&lt;br /&gt;
            $this-&amp;gt;authorname = get_string(&#039;anonymousauthor&#039;, &#039;workshop&#039;);&lt;br /&gt;
        } else {&lt;br /&gt;
            $this-&amp;gt;authorname = fullname(...);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
class mod_workshop_renderer extends plugin_renderer_base {&lt;br /&gt;
&lt;br /&gt;
    protected function render_workshop_submission(workshop_submission $submission) {&lt;br /&gt;
        $out  = $this-&amp;gt;output-&amp;gt;heading(format_string($submission-&amp;gt;title), 2);&lt;br /&gt;
        $out .= $this-&amp;gt;output-&amp;gt;container(format_string($submission-&amp;gt;authorname), &#039;author&#039;);&lt;br /&gt;
        $out .= $this-&amp;gt;output-&amp;gt;container(format_text($submission-&amp;gt;content, FORMAT_HTML), &#039;content&#039;);&lt;br /&gt;
        return $this-&amp;gt;output-&amp;gt;container($out, &#039;submission&#039;);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// in view.php&lt;br /&gt;
$output = $PAGE-&amp;gt;get_renderer(&#039;mod_workshop&#039;);&lt;br /&gt;
$submissionrecord = get_record(&#039;workshop_submissions&#039;, array(...));&lt;br /&gt;
$submissionwidget = new workshop_submission($submissionrecord, false);&lt;br /&gt;
echo $output-&amp;gt;header();&lt;br /&gt;
echo $output-&amp;gt;render($submissionwidget);&lt;br /&gt;
echo $output-&amp;gt;footer();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that $output-&amp;gt;header() and $output-&amp;gt;footer() are not defined in mod_workshop_renderer. Plugin renderers are able to forward calls to the underlying renderer automatically. So, in view.php, there could be echo $OUTPUT-&amp;gt;header(); which would call the same method. We prefer the way above because of consistency - there is the only one $output instance used instead of two $OUTPUT and $wsoutput.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;WARNING: get_renderer() should typically be the last output related call you make before displaying your page&#039;s header.&#039;&#039;&#039; get_renderer() forces the page layout to &#039;base&#039; if it has not already been set and this will almost certainly cause your page to display incorrectly (e.g. no blocks).&lt;br /&gt;
&lt;br /&gt;
Note that as of Moodle 2.9, the render method could be rewritten to use a template. See [[Templates]] for more information.&lt;br /&gt;
&lt;br /&gt;
== Bootstrap renderer ==&lt;br /&gt;
&lt;br /&gt;
Bootstrap renderer is used as a temporary $OUTPUT before the full initialisation of standard core renderer.&lt;br /&gt;
&lt;br /&gt;
== Theme customisations ==&lt;br /&gt;
&lt;br /&gt;
=== Theme renderers ===&lt;br /&gt;
&lt;br /&gt;
Theme renderers are stored in renderers.php file in theme folder (or autoloaded from the classes folder). The actual overriding of renderers is the responsibility of renderer_factory classes which may be selected in theme config.php.&lt;br /&gt;
&lt;br /&gt;
=== Creating a Renderable &amp;quot;widget&amp;quot; in your theme ===&lt;br /&gt;
&lt;br /&gt;
To create a renderable &amp;quot;widget&amp;quot; that could be used in multiple places in your theme (e.g. on the front page, in an overriden renderer for mod_assign etc.)&lt;br /&gt;
Create a renderable and renderer in the following folder:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
/theme/mytheme/output/mywidget/rendererable.php&lt;br /&gt;
theme/mytheme/output/mywidget/renderer.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mywidgetrenderable  = new \theme_mytheme\output\mywidget\renderable($params);&lt;br /&gt;
$mywidgetrenderer = $this-&amp;gt;page-&amp;gt;get_renderer(&#039;theme_mytheme&#039;,&#039;mywidget&#039;); // Or $PAGE-&amp;gt;get_renderer depending on context&lt;br /&gt;
$mywidget = $mywidgetrenderer-&amp;gt;render($mywidgetrenderable);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
there is a good example of this in moodle core -&amp;gt; /admin/tool/monitor&lt;br /&gt;
&lt;br /&gt;
== Low level HTML output ==&lt;br /&gt;
&lt;br /&gt;
Low level html markup is created via html_renderer class.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
* [[Templates]]&lt;br /&gt;
* [[Theme changes]]&lt;br /&gt;
* Using Moodle [http://moodle.org/mod/forum/discuss.php?d=177535 Totally confused by output renderers...] forum discussion&lt;br /&gt;
&lt;br /&gt;
[[Category:Themes]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Templates&amp;diff=55812</id>
		<title>Templates</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Templates&amp;diff=55812"/>
		<updated>2019-03-26T13:44:01Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Add documentation for template inferrence MDL-61869&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 simplest case where you have a renderable, templatable object with a class name matching the name of the template that will render it, you do not need to add any renderer code explicity.  Passing your widget directly to $OUTPUT-&amp;gt;renderer() will infer the name of your template, call export_for_template() and render_for_template(), then return the result.&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 your page:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$OUTPUT-&amp;gt;render($widget);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For more complex scenarios, add a render function to your component&#039;s renderer.&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;
        // Do other logic.&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>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=55607</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=55607"/>
		<updated>2019-02-25T10:19:20Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* Module prefetch handler */&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 suport 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;
: The language pack string ids used in the plugin by all the handlers. Please note that you should avoid adding all the plugin string ids (including those unused) because the Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform.&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;
&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;
==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.&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;
===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. 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. True to open in embedded browser, false to open in system browser.&lt;br /&gt;
* &#039;&#039;&#039;autoLogin&#039;&#039;&#039; (string): Optional. 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. The format is the same as in &#039;&#039;useOtherDataForWS&#039;&#039;. If not supplied, no other data will be added. If supplied but empty (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 return 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;
===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 &#039;&#039;CoreUserProfileFieldDelegate&#039;&#039; 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;
==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;
&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>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=55552</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=55552"/>
		<updated>2019-02-05T10:09:50Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* Complex example */&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 supportedin 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 suport 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;
: The language pack string ids used in the plugin by all the handlers. Please note that you should avoid adding all the plugin string ids (including those unused) because the Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform.&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;
==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;
&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;
==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.&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;
===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. 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. True to open in embedded browser, false to open in system browser.&lt;br /&gt;
* &#039;&#039;&#039;autoLogin&#039;&#039;&#039; (string): Optional. 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. The format is the same as in &#039;&#039;useOtherDataForWS&#039;&#039;. If not supplied, no other data will be added. If supplied but empty (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 return 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;
===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;mmaModCertificate&amp;quot;;&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 &#039;&#039;CoreUserProfileFieldDelegate&#039;&#039; 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;
==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;
* 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;
&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>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=55551</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=55551"/>
		<updated>2019-02-05T10:05:07Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* Complex example */&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 supportedin 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 suport 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;
: The language pack string ids used in the plugin by all the handlers. Please note that you should avoid adding all the plugin string ids (including those unused) because the Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform.&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;
==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;
&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;
==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.&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;
===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. 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. True to open in embedded browser, false to open in system browser.&lt;br /&gt;
* &#039;&#039;&#039;autoLogin&#039;&#039;&#039; (string): Optional. 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. The format is the same as in &#039;&#039;useOtherDataForWS&#039;&#039;. If not supplied, no other data will be added. If supplied but empty (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 return 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;
===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 attribute 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;mmaModCertificate&amp;quot;;&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 &#039;&#039;CoreUserProfileFieldDelegate&#039;&#039; 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;
==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;
* 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;
&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>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=55550</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=55550"/>
		<updated>2019-02-05T10:04:25Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* Complex example */&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 supportedin 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 suport 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;
: The language pack string ids used in the plugin by all the handlers. Please note that you should avoid adding all the plugin string ids (including those unused) because the Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform.&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;
==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;
&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;
==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.&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;
===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. 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. True to open in embedded browser, false to open in system browser.&lt;br /&gt;
* &#039;&#039;&#039;autoLogin&#039;&#039;&#039; (string): Optional. 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. The format is the same as in &#039;&#039;useOtherDataForWS&#039;&#039;. If not supplied, no other data will be added. If supplied but empty (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 return 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;
===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 attribute 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(cmid)) !== -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;mmaModCertificate&amp;quot;;&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 &#039;&#039;CoreUserProfileFieldDelegate&#039;&#039; 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;
==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;
* 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;
&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>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_App_Plugins_Development_Guide&amp;diff=55549</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=55549"/>
		<updated>2019-02-05T09:55:45Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Add complex examples of link handlers&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 supportedin 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 suport 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;
: The language pack string ids used in the plugin by all the handlers. Please note that you should avoid adding all the plugin string ids (including those unused) because the Web Service that returns the plugin information will include the translation of each string id for every language installed in the platform.&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;
==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;
&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;
==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.&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;
===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. 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. True to open in embedded browser, false to open in system browser.&lt;br /&gt;
* &#039;&#039;&#039;autoLogin&#039;&#039;&#039; (string): Optional. 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. The format is the same as in &#039;&#039;useOtherDataForWS&#039;&#039;. If not supplied, no other data will be added. If supplied but empty (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 (null, false or empty string) 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.&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 return 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;
===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 attribute 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(t.CoreContentLinksHandlerBase.prototype);&lt;br /&gt;
AddonModFooLinkHandler.prototype.constructor = AddonFormatOustudyplanForbiddenCourseModuleHandler;&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(cmid)) !== -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;mmaModCertificate&amp;quot;;&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 &#039;&#039;CoreUserProfileFieldDelegate&#039;&#039; 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;
==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;
* 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;
&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>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Debugging_network_requests_in_the_Moodle_App&amp;diff=54560</id>
		<title>Debugging network requests in the Moodle App</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Debugging_network_requests_in_the_Moodle_App&amp;diff=54560"/>
		<updated>2018-08-03T08:30:11Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Added instructions for creating a debuggable build of the desktop app&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile}}== Introduction ==&lt;br /&gt;
&lt;br /&gt;
This guide will help you find and report problems with the Moodle Mobile app on your site.&lt;br /&gt;
&lt;br /&gt;
It is especially useful for the following problems:&lt;br /&gt;
* Unable to log in on your site&lt;br /&gt;
* When you receive one of the following error messages in the app:&lt;br /&gt;
** Can not find data record in database table external_functions&lt;br /&gt;
** Invalid response value detected&lt;br /&gt;
** Cannot get course contents&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
* Moodle 3.0 onwards (your site needs at least this version)&lt;br /&gt;
* Google Chrome or Chromium browser&lt;br /&gt;
[[{{ns:file}}:moodlemobile_enabledebug.png|thumb|300px]]&lt;br /&gt;
&lt;br /&gt;
== Enabling debugging on your Moodle site ==&lt;br /&gt;
&lt;br /&gt;
# Go to Debugging in the Site administration&lt;br /&gt;
# For &amp;quot;Debug messages&amp;quot; select &#039;DEVELOPER&#039;&lt;br /&gt;
# Tick &amp;quot;Display debug messages&amp;quot;&lt;br /&gt;
# Click the &#039;Save changes&#039; button.&lt;br /&gt;
&lt;br /&gt;
Note: Remember to disable debugging again once you have finished debugging your problem.&lt;br /&gt;
&lt;br /&gt;
== Setting up Chrome ==&lt;br /&gt;
&lt;br /&gt;
[[{{ns:file}}:moodlemobile_chrome_inspect_network.png|thumb|300px]]&lt;br /&gt;
&lt;br /&gt;
Debugging the mobile app is not so easy, so we have provided an online web version of the app that can be easily debugged using the Chrome browser.&lt;br /&gt;
&lt;br /&gt;
# Open your Chrome browser and go to https://prototype.moodle.net/mobile/webapp/&lt;br /&gt;
# Open your browser options (icon at the top-right of your browser window), then go to More tools -&amp;gt; Developer tools&lt;br /&gt;
# Dock the new panels on the right side (in the new panel top-right options choose “Dock to the right” icon)&lt;br /&gt;
# Click the Network tab (at the top-center)&lt;br /&gt;
# Enable the filter (filter shape icon) so it changes to colour red&lt;br /&gt;
# In the new text field displayed when enabling the filter write &amp;lt;code&amp;gt;.php&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now you are ready to debug all the web services requests sent to your Moodle site by the mobile app.&lt;br /&gt;
&lt;br /&gt;
== Debugging a web service (WS) error ==&lt;br /&gt;
&lt;br /&gt;
[[{{ns:file}}:moodlemobile_chrome_debug_ws_error.png|thumb|300px]]&lt;br /&gt;
&lt;br /&gt;
# Connect to your site and browse to the functionality displaying an error&lt;br /&gt;
# In the right panel you will see a list of requests made by the app to your Moodle site (token.php server.php server.php etc..)&lt;br /&gt;
# Click on each one of them (starting with the last one in the list) but skip those that don’t start with token.php or server.php&lt;br /&gt;
# In the new sub-window open select the “Response” tab and check if you see an error&lt;br /&gt;
# Copy the error then go to the [[:en:Moodle Mobile FAQ Moodle Mobile FAQ]] in the user docs to check if there is a known solution for it&lt;br /&gt;
# If you are unable to find a solution, post in the [https://moodle.org/mod/forum/view.php?id=7798 Moodle for mobile forum] on moodle.org&lt;br /&gt;
&lt;br /&gt;
== Building Moodle Desktop with devtools enabled ==&lt;br /&gt;
&lt;br /&gt;
You may be using some features, such as Single Sign-On, that require a built app to work, rather than a hosted or local ionic build running in a web browser.&lt;br /&gt;
In this case, you can build the Moodle Desktop app with the browser&#039;s developer tools enabled.&lt;br /&gt;
&lt;br /&gt;
Note: these instructions are based on building for Windows 7. There will be differences for other environments, so please add to these instructions if you can.&lt;br /&gt;
&lt;br /&gt;
# Ensure you have a working [[Setting_up_your_development_environment_for_Moodle_Mobile_2|app development environment]]&lt;br /&gt;
# In your terminal of choice, go to the directory where you have the app&#039;s code.&lt;br /&gt;
# Edit desktop/electron.js.  Around line 66 (at the end of the createWindow() function, but inside the function), add the line:&amp;lt;br /&amp;gt;&amp;lt;code&amp;gt;mainWindow.webContents.openDevTools();&amp;lt;/code&amp;gt;&amp;lt;br /&amp;gt;It should look something like:&amp;lt;br /&amp;gt;&amp;lt;pre&amp;gt;  mainWindow.on(&#039;focus&#039;, () =&amp;gt; {&amp;amp;#10;    mainWindow.webContents.send(&#039;coreAppFocused&#039;); // Send an event to the main window.&amp;amp;#10;  });&amp;amp;#10;  mainWindow.webContents.openDevTools();&amp;amp;#10;}&amp;lt;/pre&amp;gt;&lt;br /&gt;
# Run &amp;lt;code&amp;gt;npm install -g electron electron-builder&amp;lt;/code&amp;gt;&lt;br /&gt;
# Run &amp;lt;code&amp;gt;npm install electron-builder-squirrel-windows  app-builder-lib&amp;lt;/code&amp;gt;&lt;br /&gt;
# Run &amp;lt;code&amp;gt;npm run desktop.pack&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This should give you a build of the app in desktop/dist.  For example, in windows you can run &amp;lt;code&amp;gt;desktop/dist/win-unpacked/Moodle Desktop.exe&amp;lt;/code&amp;gt;.  When the app starts, the browser devtools will run inside the app, allowing you to inspect and debug the pages.  If you close the tools, you will have to restart the app to open them again.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Moodle Mobile development using Chrome or Chromium]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=54270</id>
		<title>Setting up your development environment for the Moodle App</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=54270"/>
		<updated>2018-05-22T10:49:11Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Fix node-gyp docs link&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The majority of your development work will be done using the browser. You will likely begin to use an emulator once you need to simulate a real mobile device.&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install a browser for development&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
We recommend Chromium browser (Google Chrome open source version) https://download-chromium.appspot.com/&lt;br /&gt;
Please, read [[Moodle_Mobile_development_using_Chrome_or_Chromium]] for more information&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install git&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
https://git-scm.com/book/en/v2/Getting-Started-Installing-Git&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install Node.js&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
http://nodejs.org&lt;br /&gt;
&lt;br /&gt;
For Mac users we recommend to install NodeJS via Macports.&lt;br /&gt;
&lt;br /&gt;
Node 6.9.1 has been verified to work fine with the app. If you&#039;re having problems with later versions you might want to check if it works with 6.9.1.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;note&amp;quot;: if you also need later versions of node for other projects, use Node Version Manager aka &amp;quot;nvm&amp;quot; [https://github.com/creationix/nvm here]. Linux and OSX is supported, and the site has pointers to windows alternatives. Simply type:&lt;br /&gt;
   nvm install 6.9.1&lt;br /&gt;
   nvm use 6.9.1&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install ionic:&#039;&#039;&#039; &lt;br /&gt;
 npm cache clean&lt;br /&gt;
 npm install -g cordova ionic    # (If it throws an EACCESS error, run it again with sudo)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install the npm required packages&#039;&#039;&#039;&lt;br /&gt;
 sudo npm install -g gulp                      # (This will install gulp in a folder that should be in the PATH)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Native build dependencies for Windows&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
node-gyp requires native build tools for your platform.  If you&#039;re developing on Mac or Linux, you&#039;ll probably have these already ([https://github.com/nodejs/node-gyp/blob/master/README.md refer to the docs if not]), on Windows, run the following command as administrator (in cmd or Powershell):&lt;br /&gt;
&lt;br /&gt;
 npm install --global --production windows-build-tools&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Push notifications for Mac&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Phonegap plugin push 1.9.0 requires CocoaPods to work. The installation steps can be found in https://cocoapods.org/&lt;br /&gt;
&lt;br /&gt;
Please note that for compiling the app in Mac you need to open the .xcworkspace file, more information here: MOBILE-1970&lt;br /&gt;
&lt;br /&gt;
== Clone the app base code ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Clone the code base into a local directory in your computer.&#039;&#039;&#039;&lt;br /&gt;
 git clone https://github.com/moodlehq/moodlemobile2.git moodlemobiledirectory&lt;br /&gt;
 cd moodlemobiledirectory&lt;br /&gt;
 git checkout v3.5.0&lt;br /&gt;
&lt;br /&gt;
== Setup the environment ==&lt;br /&gt;
&lt;br /&gt;
Please, note that if you are creating a custom app with a custom URL scheme, you should edit the /package.json and /config.xml files and specify there your custom URL_SCHEME (replacing the existing value) and your [https://github.com/phonegap/phonegap-plugin-push/blob/master/docs/INSTALLATION.md GCMPN SENDER_ID].&lt;br /&gt;
&lt;br /&gt;
The following command must be run in the project&#039;s root folder:&lt;br /&gt;
 npm setup&lt;br /&gt;
&lt;br /&gt;
== Open the app in the browser ==&lt;br /&gt;
First start Chromium via the command line using the custom parameters as is mentioned here: [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
&lt;br /&gt;
and then, start the Ionic server:&lt;br /&gt;
&lt;br /&gt;
 ionic serve --browser chromium&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to open any browser you should run:&lt;br /&gt;
&lt;br /&gt;
 ionic serve -b&lt;br /&gt;
&lt;br /&gt;
== Updating ionic and cordova ==&lt;br /&gt;
&lt;br /&gt;
 sudo npm update -g cordova&lt;br /&gt;
 sudo npm update -g ionic&lt;br /&gt;
&lt;br /&gt;
Update project platforms:&lt;br /&gt;
&lt;br /&gt;
 ionic platform remove android&lt;br /&gt;
 ionic platform remove ios&lt;br /&gt;
 ionic platform add android&lt;br /&gt;
 ionic platform add ios&lt;br /&gt;
&lt;br /&gt;
== Updating plugins ==&lt;br /&gt;
&lt;br /&gt;
 cordova plugin remove your_plugin_id&lt;br /&gt;
 cordova plugin add your_plugin_id&lt;br /&gt;
&lt;br /&gt;
== Building for Android and iOS ==&lt;br /&gt;
&lt;br /&gt;
Please see this guide to be able to build for Android and iOS using the command line:&lt;br /&gt;
&lt;br /&gt;
http://cordova.apache.org/docs/en/5.0.0/guide_platforms_index.md.html#Platform%20Guides&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Common errors when building&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* com.android.dex.DexException: Multiple dex files define XXX&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations {&lt;br /&gt;
      all*.exclude group: &#039;com.android.support&#039;, module: &#039;support-v4&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
* Could not resolve all dependencies for configuration &#039;:_debugCompile&#039;.&lt;br /&gt;
Open the Android SDK Manager and make sure you have installed: Android Support Repository, Android Support Library, Google Play Services and Google Repository.&lt;br /&gt;
&lt;br /&gt;
* Could not find com.android.support:support-v4:XXX&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations.all {&lt;br /&gt;
      resolutionStrategy.force &#039;com.android.support:support-v4:24.0.0&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Error: libsass bindings not found. Try reinstalling node-sass? ===&lt;br /&gt;
&lt;br /&gt;
Please read: http://fettblog.eu/gulp-and-node4-first-aid/, alternatively you must be sure that you installed Node v0.12&lt;br /&gt;
&lt;br /&gt;
=== node-gyp\src\win_delay_load_hook.c(34): error C2373: &#039;__pfnDliNotifyHook2&#039;: redefinition; different type modifiers ===&lt;br /&gt;
&lt;br /&gt;
Try updating npm to the latest version using: &lt;br /&gt;
&lt;br /&gt;
  npm install -g npm@latest&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
http://ionicframework.com/docs/cli/&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=54269</id>
		<title>Setting up your development environment for the Moodle App</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=54269"/>
		<updated>2018-05-22T10:46:24Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Added better explanation of build dependencies&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The majority of your development work will be done using the browser. You will likely begin to use an emulator once you need to simulate a real mobile device.&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install a browser for development&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
We recommend Chromium browser (Google Chrome open source version) https://download-chromium.appspot.com/&lt;br /&gt;
Please, read [[Moodle_Mobile_development_using_Chrome_or_Chromium]] for more information&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install git&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
https://git-scm.com/book/en/v2/Getting-Started-Installing-Git&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install Node.js&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
http://nodejs.org&lt;br /&gt;
&lt;br /&gt;
For Mac users we recommend to install NodeJS via Macports.&lt;br /&gt;
&lt;br /&gt;
Node 6.9.1 has been verified to work fine with the app. If you&#039;re having problems with later versions you might want to check if it works with 6.9.1.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;note&amp;quot;: if you also need later versions of node for other projects, use Node Version Manager aka &amp;quot;nvm&amp;quot; [https://github.com/creationix/nvm here]. Linux and OSX is supported, and the site has pointers to windows alternatives. Simply type:&lt;br /&gt;
   nvm install 6.9.1&lt;br /&gt;
   nvm use 6.9.1&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install ionic:&#039;&#039;&#039; &lt;br /&gt;
 npm cache clean&lt;br /&gt;
 npm install -g cordova ionic    # (If it throws an EACCESS error, run it again with sudo)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install the npm required packages&#039;&#039;&#039;&lt;br /&gt;
 sudo npm install -g gulp                      # (This will install gulp in a folder that should be in the PATH)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Native build dependencies for Windows&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
node-gyp requires native build tools for your platform.  If you&#039;re developing on Mac or Linux, you&#039;ll probably have these already ([https://github.com/nodejs/node-gyp/blob/master/README.md|refer to the docs if not]), on Windows, run the following command as administrator (in cmd or Powershell):&lt;br /&gt;
&lt;br /&gt;
 npm install --global --production windows-build-tools&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Push notifications for Mac&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Phonegap plugin push 1.9.0 requires CocoaPods to work. The installation steps can be found in https://cocoapods.org/&lt;br /&gt;
&lt;br /&gt;
Please note that for compiling the app in Mac you need to open the .xcworkspace file, more information here: MOBILE-1970&lt;br /&gt;
&lt;br /&gt;
== Clone the app base code ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Clone the code base into a local directory in your computer.&#039;&#039;&#039;&lt;br /&gt;
 git clone https://github.com/moodlehq/moodlemobile2.git moodlemobiledirectory&lt;br /&gt;
 cd moodlemobiledirectory&lt;br /&gt;
 git checkout v3.5.0&lt;br /&gt;
&lt;br /&gt;
== Setup the environment ==&lt;br /&gt;
&lt;br /&gt;
Please, note that if you are creating a custom app with a custom URL scheme, you should edit the /package.json and /config.xml files and specify there your custom URL_SCHEME (replacing the existing value) and your [https://github.com/phonegap/phonegap-plugin-push/blob/master/docs/INSTALLATION.md GCMPN SENDER_ID].&lt;br /&gt;
&lt;br /&gt;
The following command must be run in the project&#039;s root folder:&lt;br /&gt;
 npm setup&lt;br /&gt;
&lt;br /&gt;
== Open the app in the browser ==&lt;br /&gt;
First start Chromium via the command line using the custom parameters as is mentioned here: [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
&lt;br /&gt;
and then, start the Ionic server:&lt;br /&gt;
&lt;br /&gt;
 ionic serve --browser chromium&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to open any browser you should run:&lt;br /&gt;
&lt;br /&gt;
 ionic serve -b&lt;br /&gt;
&lt;br /&gt;
== Updating ionic and cordova ==&lt;br /&gt;
&lt;br /&gt;
 sudo npm update -g cordova&lt;br /&gt;
 sudo npm update -g ionic&lt;br /&gt;
&lt;br /&gt;
Update project platforms:&lt;br /&gt;
&lt;br /&gt;
 ionic platform remove android&lt;br /&gt;
 ionic platform remove ios&lt;br /&gt;
 ionic platform add android&lt;br /&gt;
 ionic platform add ios&lt;br /&gt;
&lt;br /&gt;
== Updating plugins ==&lt;br /&gt;
&lt;br /&gt;
 cordova plugin remove your_plugin_id&lt;br /&gt;
 cordova plugin add your_plugin_id&lt;br /&gt;
&lt;br /&gt;
== Building for Android and iOS ==&lt;br /&gt;
&lt;br /&gt;
Please see this guide to be able to build for Android and iOS using the command line:&lt;br /&gt;
&lt;br /&gt;
http://cordova.apache.org/docs/en/5.0.0/guide_platforms_index.md.html#Platform%20Guides&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Common errors when building&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* com.android.dex.DexException: Multiple dex files define XXX&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations {&lt;br /&gt;
      all*.exclude group: &#039;com.android.support&#039;, module: &#039;support-v4&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
* Could not resolve all dependencies for configuration &#039;:_debugCompile&#039;.&lt;br /&gt;
Open the Android SDK Manager and make sure you have installed: Android Support Repository, Android Support Library, Google Play Services and Google Repository.&lt;br /&gt;
&lt;br /&gt;
* Could not find com.android.support:support-v4:XXX&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations.all {&lt;br /&gt;
      resolutionStrategy.force &#039;com.android.support:support-v4:24.0.0&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Error: libsass bindings not found. Try reinstalling node-sass? ===&lt;br /&gt;
&lt;br /&gt;
Please read: http://fettblog.eu/gulp-and-node4-first-aid/, alternatively you must be sure that you installed Node v0.12&lt;br /&gt;
&lt;br /&gt;
=== node-gyp\src\win_delay_load_hook.c(34): error C2373: &#039;__pfnDliNotifyHook2&#039;: redefinition; different type modifiers ===&lt;br /&gt;
&lt;br /&gt;
Try updating npm to the latest version using: &lt;br /&gt;
&lt;br /&gt;
  npm install -g npm@latest&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
http://ionicframework.com/docs/cli/&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=54268</id>
		<title>Setting up your development environment for the Moodle App</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Setting_up_your_development_environment_for_the_Moodle_App&amp;diff=54268"/>
		<updated>2018-05-22T10:37:45Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Added Windows-specific instructions to Requirements&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle Mobile}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
The majority of your development work will be done using the browser. You will likely begin to use an emulator once you need to simulate a real mobile device.&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install a browser for development&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
We recommend Chromium browser (Google Chrome open source version) https://download-chromium.appspot.com/&lt;br /&gt;
Please, read [[Moodle_Mobile_development_using_Chrome_or_Chromium]] for more information&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install git&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
https://git-scm.com/book/en/v2/Getting-Started-Installing-Git&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install Node.js&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
http://nodejs.org&lt;br /&gt;
&lt;br /&gt;
For Mac users we recommend to install NodeJS via Macports.&lt;br /&gt;
&lt;br /&gt;
Node 6.9.1 has been verified to work fine with the app. If you&#039;re having problems with later versions you might want to check if it works with 6.9.1.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;note&amp;quot;: if you also need later versions of node for other projects, use Node Version Manager aka &amp;quot;nvm&amp;quot; [https://github.com/creationix/nvm here]. Linux and OSX is supported, and the site has pointers to windows alternatives. Simply type:&lt;br /&gt;
   nvm install 6.9.1&lt;br /&gt;
   nvm use 6.9.1&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install ionic:&#039;&#039;&#039; &lt;br /&gt;
 npm cache clean&lt;br /&gt;
 npm install -g cordova ionic    # (If it throws an EACCESS error, run it again with sudo)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Install the npm required packages&#039;&#039;&#039;&lt;br /&gt;
 sudo npm install -g gulp                      # (This will install gulp in a folder that should be in the PATH)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Native build dependencies for Windows&#039;&#039;&#039;&lt;br /&gt;
 npm install --global --production windows-build-tools  # Run as administrator&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Push notifications for Mac&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Phonegap plugin push 1.9.0 requires CocoaPods to work. The installation steps can be found in https://cocoapods.org/&lt;br /&gt;
&lt;br /&gt;
Please note that for compiling the app in Mac you need to open the .xcworkspace file, more information here: MOBILE-1970&lt;br /&gt;
&lt;br /&gt;
== Clone the app base code ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Clone the code base into a local directory in your computer.&#039;&#039;&#039;&lt;br /&gt;
 git clone https://github.com/moodlehq/moodlemobile2.git moodlemobiledirectory&lt;br /&gt;
 cd moodlemobiledirectory&lt;br /&gt;
 git checkout v3.5.0&lt;br /&gt;
&lt;br /&gt;
== Setup the environment ==&lt;br /&gt;
&lt;br /&gt;
Please, note that if you are creating a custom app with a custom URL scheme, you should edit the /package.json and /config.xml files and specify there your custom URL_SCHEME (replacing the existing value) and your [https://github.com/phonegap/phonegap-plugin-push/blob/master/docs/INSTALLATION.md GCMPN SENDER_ID].&lt;br /&gt;
&lt;br /&gt;
The following command must be run in the project&#039;s root folder:&lt;br /&gt;
 npm setup&lt;br /&gt;
&lt;br /&gt;
== Open the app in the browser ==&lt;br /&gt;
First start Chromium via the command line using the custom parameters as is mentioned here: [[Moodle Mobile development using Chrome or Chromium]]&lt;br /&gt;
&lt;br /&gt;
and then, start the Ionic server:&lt;br /&gt;
&lt;br /&gt;
 ionic serve --browser chromium&lt;br /&gt;
&lt;br /&gt;
If you don&#039;t want to open any browser you should run:&lt;br /&gt;
&lt;br /&gt;
 ionic serve -b&lt;br /&gt;
&lt;br /&gt;
== Updating ionic and cordova ==&lt;br /&gt;
&lt;br /&gt;
 sudo npm update -g cordova&lt;br /&gt;
 sudo npm update -g ionic&lt;br /&gt;
&lt;br /&gt;
Update project platforms:&lt;br /&gt;
&lt;br /&gt;
 ionic platform remove android&lt;br /&gt;
 ionic platform remove ios&lt;br /&gt;
 ionic platform add android&lt;br /&gt;
 ionic platform add ios&lt;br /&gt;
&lt;br /&gt;
== Updating plugins ==&lt;br /&gt;
&lt;br /&gt;
 cordova plugin remove your_plugin_id&lt;br /&gt;
 cordova plugin add your_plugin_id&lt;br /&gt;
&lt;br /&gt;
== Building for Android and iOS ==&lt;br /&gt;
&lt;br /&gt;
Please see this guide to be able to build for Android and iOS using the command line:&lt;br /&gt;
&lt;br /&gt;
http://cordova.apache.org/docs/en/5.0.0/guide_platforms_index.md.html#Platform%20Guides&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Common errors when building&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* com.android.dex.DexException: Multiple dex files define XXX&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations {&lt;br /&gt;
      all*.exclude group: &#039;com.android.support&#039;, module: &#039;support-v4&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
* Could not resolve all dependencies for configuration &#039;:_debugCompile&#039;.&lt;br /&gt;
Open the Android SDK Manager and make sure you have installed: Android Support Repository, Android Support Library, Google Play Services and Google Repository.&lt;br /&gt;
&lt;br /&gt;
* Could not find com.android.support:support-v4:XXX&lt;br /&gt;
Open the file &#039;&#039;platforms/android/build.gradle&#039;&#039; and add this code at the end:&lt;br /&gt;
&lt;br /&gt;
  configurations.all {&lt;br /&gt;
      resolutionStrategy.force &#039;com.android.support:support-v4:24.0.0&#039;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Error: libsass bindings not found. Try reinstalling node-sass? ===&lt;br /&gt;
&lt;br /&gt;
Please read: http://fettblog.eu/gulp-and-node4-first-aid/, alternatively you must be sure that you installed Node v0.12&lt;br /&gt;
&lt;br /&gt;
=== node-gyp\src\win_delay_load_hook.c(34): error C2373: &#039;__pfnDliNotifyHook2&#039;: redefinition; different type modifiers ===&lt;br /&gt;
&lt;br /&gt;
Try updating npm to the latest version using: &lt;br /&gt;
&lt;br /&gt;
  npm install -g npm@latest&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
http://ionicframework.com/docs/cli/&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Tag_API&amp;diff=53688</id>
		<title>Tag API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Tag_API&amp;diff=53688"/>
		<updated>2018-02-09T09:59:21Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* Defining a tag area */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.1}}&lt;br /&gt;
&lt;br /&gt;
==Tag API overview==&lt;br /&gt;
&lt;br /&gt;
The Tag API allows you to assign labels to information in Moodle. This makes finding this information easier and also facilitates the grouping of similar information. The Tag API allows you to create, modify, delete and search tags in the Moodle system. The main tag related functions can be found in the tag/classes/tag.php file. For a through overview of all of the functions available for working with Tags please see methods in core_tag_tag, core_tag_collection and core_tag_area classes, however the following examples should give you a general understanding of how to get started with tags.&lt;br /&gt;
&lt;br /&gt;
This page describes API in Moodle 3.1 and above, for earlier versions see [[Tag API before 3.1]]&lt;br /&gt;
&lt;br /&gt;
==Tag API usage==&lt;br /&gt;
&lt;br /&gt;
When user tags something a &#039;&#039;&#039;tag instance&#039;&#039;&#039; is created in the database linking the item to the actual &#039;&#039;&#039;tag&#039;&#039;&#039;. If tag did not exist before it is created automatically. Do not confuse these two entities - deleting the tag instance does not normally delete tag, however deleting tag deletes all tag instances associated with it. &lt;br /&gt;
&lt;br /&gt;
Developers define &#039;&#039;&#039;tag areas&#039;&#039;&#039; the areas that can be tagged, examples are:&lt;br /&gt;
* blog posts&lt;br /&gt;
* courses&lt;br /&gt;
* users (tags represent user interests)&lt;br /&gt;
* activity modules&lt;br /&gt;
* questions&lt;br /&gt;
* wiki pages&lt;br /&gt;
&lt;br /&gt;
Each tag area is identified by two attributes - component and itemtype. &#039;&#039;Itemtype must be a name of a table in the database.&#039;&#039; Component is the core component or plugin responsible for the tagging. This way the same DB table (for example &#039;user&#039; or &#039;course&#039;) may be independently tagged by several components. Administrator or manager is able to manage the tag areas, collections and tags inside them on the [https://docs.moodle.org/en/Managing_tags Managing tags] page. Users are able to search tags and view all items tagged with them that they have access to.&lt;br /&gt;
&lt;br /&gt;
===Defining a tag area===&lt;br /&gt;
&lt;br /&gt;
First, developer must define the tag areas in the file &#039;&#039;&#039;db/tag.php&#039;&#039;&#039;. This will usually look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$tagareas = array(&lt;br /&gt;
    array(&lt;br /&gt;
        &#039;itemtype&#039; =&amp;gt; &#039;wiki_pages&#039;,  // This must be a name of the database table (without prefix).&lt;br /&gt;
        &#039;component&#039; =&amp;gt; &#039;mod_wiki&#039;, // This can be omitted for plugins since it can only be full frankenstyle name of the plugin.&lt;br /&gt;
        &#039;callback&#039; =&amp;gt; &#039;mod_wiki\tags::get_tagged_pages&#039;&lt;br /&gt;
    ),&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
You will also need to add language string, for the example above it will be &#039;&#039;&#039;$string[&#039;tagarea_wiki_pages&#039;] = &#039;Wiki pages&#039;;&#039;&#039;&#039;, and define the callback function, for the example above it will be &amp;lt;tt&amp;gt;get_tagged_pages()&amp;lt;/tt&amp;gt; in the &amp;lt;tt&amp;gt;tags&amp;lt;/tt&amp;gt; class, defined in &amp;lt;tt&amp;gt;mod/wiki/classes/tags.php&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
There are more options such as specifying the default value for &amp;quot;Standard tags&amp;quot;, having a fixed collection or excluding from search. They can be found in comments in [[https://github.com/moodle/moodle/blob/master/lib/db/tag.php lib/db/tag.php]]&lt;br /&gt;
&lt;br /&gt;
After making changes to db/tag.php plugin developer must bump the plugin version in &#039;&#039;&#039;version.php&#039;&#039;&#039; and run upgrade script. This usually applies to any changes in the db/ folder.&lt;br /&gt;
&lt;br /&gt;
===Adding tags element to the editing form===&lt;br /&gt;
&lt;br /&gt;
After tag area is defined it should appear on &amp;quot;Manage tags&amp;quot; page. Now it is time to allow users to add/change tags when editing the item. Here is an example from Wiki module:&lt;br /&gt;
&lt;br /&gt;
1. Add a &#039;tags&#039; form element to the editing form:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;tags&#039;, &#039;tags&#039;, get_string(&#039;tags&#039;), array(&#039;itemtype&#039; =&amp;gt; &#039;wiki_pages&#039;, &#039;component&#039; =&amp;gt; &#039;mod_wiki&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This element will automatically check if the tag area is disabled by the manager and will not display anything in this case. However if you want to add a header you need to check if tag area is enabled add this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (core_tag_tag::is_enabled(&#039;mod_wiki&#039;, &#039;wiki_pages&#039;)) {&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;header&#039;, &#039;tagshdr&#039;, get_string(&#039;tags&#039;, &#039;tag&#039;));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. Save the form data&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($data = $form-&amp;gt;get_data()) {&lt;br /&gt;
    // Do some other processing here, if this is a new page (item) you need to insert it in the DB and obtain id.&lt;br /&gt;
    // $pageid = $data-&amp;gt;id;&lt;br /&gt;
    core_tag_tag::set_item_tags(&#039;mod_wiki&#039;, &#039;wiki_pages&#039;, $pageid, $modulecontext, $data-&amp;gt;tags);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
It is important to specify the correct context in this function. Note that $data-&amp;gt;tags will always be returned by the form, even if the area is disabled, however core_tag_tag::set_item_tags() will not change or reset tags if the tag area is disabled.&lt;br /&gt;
&lt;br /&gt;
3. Populate the form with existing tags before calling $form-&amp;gt;set_data($data):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$data = $DB-&amp;gt;get_record(&#039;wiki_pages&#039;, array(&#039;id&#039; =&amp;gt; $this-&amp;gt;page-&amp;gt;id)); // Well, it&#039;s more complicated than that of course....&lt;br /&gt;
$data-&amp;gt;tags = core_tag_tag::get_item_tags_array(&#039;mod_wiki&#039;, &#039;wiki_pages&#039;, $this-&amp;gt;page-&amp;gt;id);&lt;br /&gt;
$form-&amp;gt;set_data($data);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Always test the code with tag area enabled and disabled.&lt;br /&gt;
&lt;br /&gt;
===Displaying tags next to the item===&lt;br /&gt;
&lt;br /&gt;
Example of displaying of the tags are user interests on the user profile page. User can see the list of interests, each of them is a link that leads to the page that shows all items tagged with this tag.&lt;br /&gt;
&lt;br /&gt;
Here is the code used by Wiki module to display tags the page is tagged with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
echo $OUTPUT-&amp;gt;tag_list(core_tag_tag::get_item_tags(&#039;mod_wiki&#039;, &#039;wiki_pages&#039;, $page-&amp;gt;id), null, &#039;wiki-tags&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Deleting and clearing tags===&lt;br /&gt;
&lt;br /&gt;
Cron will automatically remove tag instances that point to non existing items, however it is a good habit to delete tags when the record is deleted.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Please note:&#039;&#039;&#039; If you have created a course activity that uses tags you should also remember to delete the tags during a course reset by adding code to the reset course callbacks. First you want to add a checkbox that a user can check if they wish to delete the tags, then code that handles the case when the checkbox has been checked. Example -&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * The elements to add the course reset form.&lt;br /&gt;
 *&lt;br /&gt;
 * @param moodleform $mform&lt;br /&gt;
 */&lt;br /&gt;
function book_reset_course_form_definition(&amp;amp;$mform) {&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;header&#039;, &#039;bookheader&#039;, get_string(&#039;modulenameplural&#039;, &#039;book&#039;));&lt;br /&gt;
    $mform-&amp;gt;addElement(&#039;checkbox&#039;, &#039;reset_book_tags&#039;, get_string(&#039;removeallbooktags&#039;, &#039;book&#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * This function is used by the reset_course_userdata function in moodlelib.&lt;br /&gt;
 * @param $data the data submitted from the reset course.&lt;br /&gt;
 * @return array status array&lt;br /&gt;
 */&lt;br /&gt;
function book_reset_userdata($data) {&lt;br /&gt;
    global $DB;&lt;br /&gt;
&lt;br /&gt;
    $status = [];&lt;br /&gt;
&lt;br /&gt;
    if (!empty($data-&amp;gt;reset_book_tags)) {&lt;br /&gt;
        // Loop through the books and remove the tags from the chapters.&lt;br /&gt;
        if ($books = $DB-&amp;gt;get_records(&#039;book&#039;, array(&#039;course&#039; =&amp;gt; $data-&amp;gt;courseid))) {&lt;br /&gt;
            foreach ($books as $book) {&lt;br /&gt;
                if (!$cm = get_coursemodule_from_instance(&#039;book&#039;, $book-&amp;gt;id)) {&lt;br /&gt;
                    continue;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                $context = context_module::instance($cm-&amp;gt;id);&lt;br /&gt;
                core_tag_tag::delete_instances(&#039;mod_book&#039;, null, $context-&amp;gt;id);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $status[] = [&lt;br /&gt;
            &#039;component&#039; =&amp;gt; get_string(&#039;modulenameplural&#039;, &#039;book&#039;),&lt;br /&gt;
            &#039;item&#039; =&amp;gt; get_string(&#039;tagsdeleted&#039;, &#039;book&#039;),&lt;br /&gt;
            &#039;error&#039; =&amp;gt; false&lt;br /&gt;
        ];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return $status;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The functions used to delete the tags are -&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
core_tag_tag::remove_all_item_tags($component, $itemtype, $itemid, $tiuserid = 0) // Removes all tag instances associated with an item.&lt;br /&gt;
core_tag_tag::delete_instances($component, $itemtype = null, $contextid = null) // Removes tag instances in bulk. Here $component is mandatory in this method, either itemtype or contextid or neither or both can be specified.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Backup and restore===&lt;br /&gt;
&lt;br /&gt;
When you tag contents inside the course the plugin has to hook into backup and restore and process necessary tags. This is especially important for the contents inside activity modules, such as wiki pages or forum posts. Questions in the course question bank also backup and restore their tags.&lt;br /&gt;
&lt;br /&gt;
You can choose to backup and restore tags for each item individually (as it is done in mod_wiki) OR backup all tags in the context at once (as it is done in mod_glossary or mod_forum). Second option is preferable for performance reasons. Make sure to take into account $userinfo (whether user information is backed up / restored), for example wiki pages is not user information but glossary entries are, tags on them follow the same rule.&lt;br /&gt;
&lt;br /&gt;
This is an example from Glossary module that can be found in &#039;&#039;&#039;backup_glossary_stepslib.php&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
protected function define_structure() {&lt;br /&gt;
    // ...&lt;br /&gt;
    $tags = new backup_nested_element(&#039;entriestags&#039;);&lt;br /&gt;
    $tag = new backup_nested_element(&#039;tag&#039;, array(&#039;id&#039;), array(&#039;itemid&#039;, &#039;rawname&#039;));&lt;br /&gt;
    // ...&lt;br /&gt;
    $glossary-&amp;gt;add_child($tags); // Note that parent node is the glossary, not glossary entry, however the individual entries are tagged.&lt;br /&gt;
    $tags-&amp;gt;add_child($tag);&lt;br /&gt;
    // ...&lt;br /&gt;
    if ($userinfo &amp;amp;&amp;amp; core_tag_tag::is_enabled(&#039;mod_glossary&#039;, &#039;glossary_entries&#039;)) {&lt;br /&gt;
        $tag-&amp;gt;set_source_sql(&#039;SELECT t.id, ti.itemid, t.rawname&lt;br /&gt;
                                FROM {tag} t&lt;br /&gt;
                                JOIN {tag_instance} ti ON ti.tagid = t.id&lt;br /&gt;
                               WHERE ti.itemtype = ?&lt;br /&gt;
                                 AND ti.component = ?&lt;br /&gt;
                                 AND ti.contextid = ?&#039;, array(&lt;br /&gt;
            backup_helper::is_sqlparam(&#039;glossary_entries&#039;),&lt;br /&gt;
            backup_helper::is_sqlparam(&#039;mod_glossary&#039;),&lt;br /&gt;
            backup::VAR_CONTEXTID));&lt;br /&gt;
    }&lt;br /&gt;
    // ...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
And this in the &#039;&#039;&#039;restore_glossary_stepslib.php&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
protected function define_structure() {&lt;br /&gt;
    // ...&lt;br /&gt;
    if ($userinfo) {&lt;br /&gt;
        $paths[] = new restore_path_element(&#039;glossary_entry_tag&#039;, &#039;/activity/glossary/entriestags/tag&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    // ...&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
protected function process_glossary_entry_tag($data) {&lt;br /&gt;
    $data = (object)$data;&lt;br /&gt;
&lt;br /&gt;
    if (!core_tag_tag::is_enabled(&#039;mod_glossary&#039;, &#039;glossary_entries&#039;)) { // Tags disabled on this site, nothing to process.&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $tag = $data-&amp;gt;rawname;&lt;br /&gt;
    if (!$itemid = $this-&amp;gt;get_mappingid(&#039;glossary_entry&#039;, $data-&amp;gt;itemid)) {&lt;br /&gt;
        // Orphaned tag, we could not find the glossary entry for it - ignore.&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    $context = context_module::instance($this-&amp;gt;task-&amp;gt;get_moduleid());&lt;br /&gt;
    core_tag_tag::add_item_tag(&#039;mod_glossary&#039;, &#039;glossary_entries&#039;, $itemid, $context, $tag);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Search callback===&lt;br /&gt;
&lt;br /&gt;
This is the most difficult part of Tag API. When user searches for the items tagged with a specific tag only the items user has access to must be returned. Running access check on all items can be very costly. Class &#039;&#039;&#039;core_tag_index_builder&#039;&#039;&#039; can help with retrieving and caching records, especially inside the courses or activity modules.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function mod_wiki_get_tagged_pages($tag, $exclusivemode = false, $fromctx = 0, $ctx = 0, $rec = 1, $page = 0) {&lt;br /&gt;
    // Find items.&lt;br /&gt;
    // Please refer to existing callbacks in core for examples.&lt;br /&gt;
&lt;br /&gt;
    // ...&lt;br /&gt;
&lt;br /&gt;
    // Use core_tag_index_builder to build and filter the list of items. &lt;br /&gt;
    // Notice how we search for 6 items when we need to display 5 - this way we will know that we need to display a link to the next page.&lt;br /&gt;
    $builder = new core_tag_index_builder(&#039;mod_wiki&#039;, &#039;wiki_pages&#039;, $query, $params, $page * $perpage, $perpage + 1);&lt;br /&gt;
&lt;br /&gt;
    // ...&lt;br /&gt;
&lt;br /&gt;
    $items = $builder-&amp;gt;get_items();&lt;br /&gt;
    if (count($items) &amp;gt; $perpage) {&lt;br /&gt;
        $totalpages = $page + 2; // We don&#039;t need exact page count, just indicate that the next page exists.&lt;br /&gt;
        array_pop($items);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Build the display contents.&lt;br /&gt;
    if ($items) {&lt;br /&gt;
        $tagfeed = new core_tag\output\tagfeed();&lt;br /&gt;
        foreach ($items as $item) {&lt;br /&gt;
            $tagfeed-&amp;gt;add(...);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        $content = $OUTPUT-&amp;gt;render_from_template(&#039;core_tag/tagfeed&#039;, $tagfeed-&amp;gt;export_for_template($OUTPUT));&lt;br /&gt;
&lt;br /&gt;
        return new core_tag\output\tagindex($tag, &#039;mod_wiki&#039;, &#039;wiki_pages&#039;, $content,&lt;br /&gt;
                $exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===User-specific tags===&lt;br /&gt;
&lt;br /&gt;
It is possible that each tagged item can be tagged by each user independently. Before Moodle 3.0 this was how courses were tagged however from 3.0 tagging courses became more standard. The functionality remains in the API (argument $tiuser to the tagging functions).  In this case both tag list and tag cloud will display all tag instances added by all users but each user will be able to edit only their own.&lt;br /&gt;
&lt;br /&gt;
If developer chooses to implement it in the plugin, they need to also implement the UI when admin or other privileged user can remove or edit the instances of another user. Otherwise if teacher has tagged the course and later resigned there will be no way to change their tags. Also such tag instances need special treatment during backup and restore - they are now considered &amp;quot;user data&amp;quot; and user id mapping should be performed.&lt;br /&gt;
&lt;br /&gt;
===Advanced usages===&lt;br /&gt;
&lt;br /&gt;
Custom plugins may go beyond the standard tags handling and use them without mixing with regular course/user/wiki/blogs tags, hide them from the &amp;quot;Tag search&amp;quot; page and &amp;quot;Tags&amp;quot; block but instead have their own interface to search/categorise using tags. This can be achieved by defining tag collection, make it not searchable and specify a custom URL to link to when the tag is actually displayed. This all can be defined in db/tag.php, see the comments in [https://github.com/moodle/moodle/blob/master/lib/db/tag.php lib/db/tag.php]&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;tag_area::get_collection($component, $itemtype)&#039;&#039;&#039; - will return you the collection that your tag area is in&lt;br /&gt;
* &#039;&#039;&#039;tag_collection::get_tag_cloud()&#039;&#039;&#039; - will return all tags in the collection. There are no API methods to get the tags used in the tag area - this is actually the purpose of tag collections.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
* [[Tag API before 3.1]]&lt;br /&gt;
* [https://docs.moodle.org/en/Managing_tags Managing tags]&lt;br /&gt;
&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Libraries_Organization&amp;diff=53686</id>
		<title>Libraries Organization</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Libraries_Organization&amp;diff=53686"/>
		<updated>2018-02-09T09:34:12Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* widget/locallib.php */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.0}}&lt;br /&gt;
This document defines standard library structures to keep Moodle readable and organised.&lt;br /&gt;
&lt;br /&gt;
= Structure =&lt;br /&gt;
&lt;br /&gt;
There are several standard locations for library code in Moodle. &lt;br /&gt;
&lt;br /&gt;
==Simple core library==&lt;br /&gt;
&lt;br /&gt;
If the widget feature is a very simple core library with no GUI scripts (like formslib) then do it like this:&lt;br /&gt;
&lt;br /&gt;
====lib/widgetlib.php====&lt;br /&gt;
&lt;br /&gt;
::This contains the main library functions.  New ones should always implement these as a class.  &lt;br /&gt;
&lt;br /&gt;
====lib/widget====&lt;br /&gt;
&lt;br /&gt;
::Any supporting files for this library can go in this sub-directory.  This directory should not contain any GUI scripts and files from here should NOT be directly included in Moodle code.&lt;br /&gt;
&lt;br /&gt;
==Plugins and complex core libraries==&lt;br /&gt;
&lt;br /&gt;
If the feature has some GUI scripts then it should have its own directory (eg like blog, comment, mod/forum, local/widget etc) and then this is always applies:&lt;br /&gt;
&lt;br /&gt;
====widget/classes====&lt;br /&gt;
::for classes that will be used by scripts in widget directory.  See [[Automatic class loading]] for more information.&lt;br /&gt;
&lt;br /&gt;
====widget/lib.php====&lt;br /&gt;
::the main library file for the module containing functions that are either required by core code (eg forum_cron()), or simply being made available to other Moodle code (eg comment/lib.php).  Generally there will be no capability checks in these functions.&lt;br /&gt;
====widget/externallib.php====&lt;br /&gt;
::for functions and classes implementing external interface for use by third-party systems like web services, and may also be used by other modules in Moodle. These functions are a very stable API and implement full capability checking.&lt;br /&gt;
&lt;br /&gt;
= Refactoring of old code = &lt;br /&gt;
&lt;br /&gt;
This is some code that was refactored in 2.0 to fit the above guidelines. &lt;br /&gt;
&lt;br /&gt;
==Groups==&lt;br /&gt;
&lt;br /&gt;
===currently in group/lib.php===&lt;br /&gt;
* groups_add_member($grouporid, $userorid)&lt;br /&gt;
* groups_remove_member($grouporid, $userorid)&lt;br /&gt;
* groups_create_group($data, $editform=false, $editoroptions=null)&lt;br /&gt;
* groups_create_grouping($data, $editoroptions=null)&lt;br /&gt;
* groups_update_group($data, $editform=false)&lt;br /&gt;
* groups_update_grouping($data, $editoroptions=null)&lt;br /&gt;
* groups_delete_group($grouporid)&lt;br /&gt;
* groups_delete_grouping($groupingorid)&lt;br /&gt;
* groups_delete_group_members($courseid, $userid=0, $showfeedback=false)&lt;br /&gt;
* groups_delete_groupings_groups($courseid, $showfeedback=false)&lt;br /&gt;
* groups_delete_groups($courseid, $showfeedback=false)&lt;br /&gt;
* groups_delete_groupings($courseid, $showfeedback=false)&lt;br /&gt;
* groups_get_possible_roles($context)&lt;br /&gt;
* groups_get_potential_members($courseid, $roleid = null, $orderby = &#039;lastname,firstname&#039;)&lt;br /&gt;
* groups_parse_name($format, $groupnumber)&lt;br /&gt;
* groups_assign_grouping($groupingid, $groupid)&lt;br /&gt;
* groups_unassign_grouping($groupingid, $groupid)&lt;br /&gt;
* groups_get_members_by_role($groupid, $courseid, $fields=&#039;u.*&#039;, $sort=&#039;u.lastname ASC&#039;, $extrawheretest=&#039;&#039;, $whereparams=array())&lt;br /&gt;
* groups_calculate_role_people($rs, $context)&lt;br /&gt;
&lt;br /&gt;
===currently in lib/grouplib.php===&lt;br /&gt;
* groups_group_exists($groupid)&lt;br /&gt;
* groups_get_group_name($groupid)&lt;br /&gt;
* groups_get_grouping_name($groupingid)&lt;br /&gt;
* groups_get_group_by_name($courseid, $name)&lt;br /&gt;
* groups_get_grouping_by_name($courseid, $name)&lt;br /&gt;
* groups_get_group($groupid, $fields=&#039;*&#039;, $strictness=IGNORE_MISSING)&lt;br /&gt;
* groups_get_grouping($groupingid, $fields=&#039;*&#039;, $strictness=IGNORE_MISSING)&lt;br /&gt;
* groups_get_all_groups($courseid, $userid=0, $groupingid=0, $fields=&#039;g.*&#039;)&lt;br /&gt;
* groups_get_user_groups($courseid, $userid=0)&lt;br /&gt;
* groups_get_all_groupings($courseid)&lt;br /&gt;
* groups_is_member($groupid, $userid=null)&lt;br /&gt;
* groups_has_membership($cm, $userid=null)&lt;br /&gt;
* groups_get_members($groupid, $fields=&#039;u.*&#039;, $sort=&#039;lastname ASC&#039;)&lt;br /&gt;
* groups_get_grouping_members($groupingid, $fields=&#039;u.*&#039;, $sort=&#039;lastname ASC&#039;)&lt;br /&gt;
* groups_get_course_groupmode($course)&lt;br /&gt;
* groups_get_activity_groupmode($cm, $course=null)&lt;br /&gt;
* groups_print_course_menu($course, $urlroot, $return=false)&lt;br /&gt;
* groups_print_activity_menu($cm, $urlroot, $return=false, $hideallparticipants=false)&lt;br /&gt;
* groups_get_course_group($course, $update=false)&lt;br /&gt;
* groups_get_activity_group($cm, $update=false)&lt;br /&gt;
* groups_get_activity_allowed_groups($cm,$userid=0)&lt;br /&gt;
* groups_course_module_visible($cm, $userid=null)&lt;br /&gt;
&lt;br /&gt;
=== New group/lib.php ===&lt;br /&gt;
* groups_add_member($grouporid, $userorid)&lt;br /&gt;
* groups_remove_member($grouporid, $userorid)&lt;br /&gt;
* groups_create_group($data, $editform=false, $editoroptions=null)&lt;br /&gt;
* groups_create_grouping($data, $editoroptions=null)&lt;br /&gt;
* groups_update_group($data, $editform=false)&lt;br /&gt;
* groups_update_grouping($data, $editoroptions=null)&lt;br /&gt;
* groups_delete_group($grouporid)&lt;br /&gt;
* groups_delete_grouping($groupingorid)&lt;br /&gt;
* groups_delete_group_members($courseid, $userid=0, $showfeedback=false)&lt;br /&gt;
* groups_delete_groupings_groups($courseid, $showfeedback=false)&lt;br /&gt;
* groups_delete_groups($courseid, $showfeedback=false)&lt;br /&gt;
* groups_delete_groupings($courseid, $showfeedback=false)&lt;br /&gt;
* groups_assign_grouping($groupingid, $groupid)&lt;br /&gt;
* groups_unassign_grouping($groupingid, $groupid)&lt;br /&gt;
* groups_get_members_by_role($groupid, $courseid, $fields=&#039;u.*&#039;, $sort=&#039;u.lastname ASC&#039;, $extrawheretest=&#039;&#039;, $whereparams=array())&lt;br /&gt;
* groups_group_exists($groupid)&lt;br /&gt;
* groups_get_group_name($groupid)&lt;br /&gt;
* groups_get_grouping_name($groupingid)&lt;br /&gt;
* groups_get_group_by_name($courseid, $name)&lt;br /&gt;
* groups_get_grouping_by_name($courseid, $name)&lt;br /&gt;
* groups_get_group($groupid, $fields=&#039;*&#039;, $strictness=IGNORE_MISSING)&lt;br /&gt;
* groups_get_grouping($groupingid, $fields=&#039;*&#039;, $strictness=IGNORE_MISSING)&lt;br /&gt;
* groups_get_all_groups($courseid, $userid=0, $groupingid=0, $fields=&#039;g.*&#039;)&lt;br /&gt;
* groups_get_user_groups($courseid, $userid=0)&lt;br /&gt;
* groups_get_all_groupings($courseid)&lt;br /&gt;
* groups_is_member($groupid, $userid=null)&lt;br /&gt;
* groups_has_membership($cm, $userid=null)&lt;br /&gt;
* groups_get_members($groupid, $fields=&#039;u.*&#039;, $sort=&#039;lastname ASC&#039;)&lt;br /&gt;
* groups_get_grouping_members($groupingid, $fields=&#039;u.*&#039;, $sort=&#039;lastname ASC&#039;)&lt;br /&gt;
* groups_get_course_groupmode($course)&lt;br /&gt;
* groups_get_activity_groupmode($cm, $course=null)&lt;br /&gt;
* groups_get_course_group($course, $update=false)&lt;br /&gt;
* groups_get_activity_group($cm, $update=false)&lt;br /&gt;
* groups_get_activity_allowed_groups($cm,$userid=0)&lt;br /&gt;
* groups_print_course_menu($course, $urlroot, $return=false)&lt;br /&gt;
* groups_print_activity_menu($cm, $urlroot, $return=false, $hideallparticipants=false)&lt;br /&gt;
* groups_course_module_visible($cm, $userid=null)&lt;br /&gt;
&lt;br /&gt;
=== New group/locallib.php ===&lt;br /&gt;
groups_get_possible_roles($context)&amp;lt;br/&amp;gt;&lt;br /&gt;
groups_get_potential_members($courseid, $roleid = null, $orderby = &#039;lastname,firstname&#039;)&amp;lt;br/&amp;gt;&lt;br /&gt;
groups_parse_name($format, $groupnumber)&amp;lt;br/&amp;gt;&lt;br /&gt;
groups_calculate_role_people($rs, $context)&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Users==&lt;br /&gt;
&lt;br /&gt;
===Currently in lib/datalib.php ===&lt;br /&gt;
* get_users($get=true, $search=&#039;&#039;, $confirmed=false, array $exceptions=null, $sort=&#039;firstname ASC&#039;, $firstinitial=&#039;&#039;, $lastinitial=&#039;&#039;, $page=&#039;&#039;, $recordsperpage=&#039;&#039;, $fields=&#039;*&#039;, $extraselect=&#039;&#039;, array $extraparams=null) &amp;lt;font color=&amp;quot;red&amp;quot;&amp;gt;New for triage&amp;lt;/font&amp;gt;&lt;br /&gt;
* get_users_confirmed()&amp;lt;font color=&amp;quot;red&amp;quot;&amp;gt;New for triage&amp;lt;/font&amp;gt;&lt;br /&gt;
* get_users_listing($sort=&#039;lastaccess&#039;, $dir=&#039;ASC&#039;, $page=0, $recordsperpage=0, $search=&#039;&#039;, $firstinitial=&#039;&#039;, $lastinitial=&#039;&#039;, $extraselect=&#039;&#039;, array $extraparams=null)&amp;lt;font color=&amp;quot;red&amp;quot;&amp;gt;New for triage&amp;lt;/font&amp;gt;&lt;br /&gt;
* search_users($courseid, $groupid, $searchtext, $sort=&#039;&#039;, array $exceptions=null)&amp;lt;font color=&amp;quot;red&amp;quot;&amp;gt;New for triage&amp;lt;/font&amp;gt;&lt;br /&gt;
* user_accesstime_log($courseid=0)&amp;lt;font color=&amp;quot;red&amp;quot;&amp;gt;New for triage&amp;lt;/font&amp;gt;&lt;br /&gt;
* user_can_create_courses()&amp;lt;font color=&amp;quot;red&amp;quot;&amp;gt;New for triage&amp;lt;/font&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Currently in user/profile/lib.php ===&lt;br /&gt;
* profile_user_record($userid) &amp;lt;font color=&amp;quot;red&amp;quot;&amp;gt;New for triage&amp;lt;/font&amp;gt;&lt;br /&gt;
* profile_load_data(&amp;amp;$user)&amp;lt;font color=&amp;quot;red&amp;quot;&amp;gt;New for triage&amp;lt;/font&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Currently in lib/moodlelib.php ===&lt;br /&gt;
authenticate_user_login($username, $password)&amp;lt;br/&amp;gt;&lt;br /&gt;
calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL)&amp;lt;br/&amp;gt;&lt;br /&gt;
check_user_preferences_loaded($time = null)&amp;lt;br/&amp;gt;&lt;br /&gt;
complete_user_login($user, $setcookie=true)&amp;lt;br/&amp;gt;&lt;br /&gt;
create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null)&amp;lt;br/&amp;gt;&lt;br /&gt;
create_user_record($username, $password, $auth=&#039;manual&#039;)&amp;lt;br/&amp;gt;&lt;br /&gt;
delete_user($user)&amp;lt;br/&amp;gt;&lt;br /&gt;
get_complete_user_data($field, $value, $mnethostid=null)&amp;lt;br/&amp;gt;&lt;br /&gt;
get_user_directories($only_non_empty=true, $legacy=false)&amp;lt;br/&amp;gt;&lt;br /&gt;
get_user_fieldnames()&amp;lt;br/&amp;gt;&lt;br /&gt;
get_user_preferences($name=NULL, $default=NULL, $otheruserid=NULL)&amp;lt;br/&amp;gt;&lt;br /&gt;
get_user_timezone($tz = 99)&amp;lt;br/&amp;gt;&lt;br /&gt;
get_user_timezone_offset($tz = 99)&amp;lt;br/&amp;gt;&lt;br /&gt;
get_users_from_config($value, $capability)&amp;lt;br/&amp;gt;&lt;br /&gt;
guest_user()&amp;lt;br/&amp;gt;&lt;br /&gt;
hash_internal_user_password($password)&amp;lt;br/&amp;gt;&lt;br /&gt;
is_restored_user($username)&amp;lt;br/&amp;gt;&lt;br /&gt;
isguestuser($user=NULL)&amp;lt;br/&amp;gt;&lt;br /&gt;
isloggedin()&amp;lt;br/&amp;gt;&lt;br /&gt;
make_user_directory($userid, $test=false)&amp;lt;br/&amp;gt;&lt;br /&gt;
mark_user_preferences_changed($userid)&amp;lt;br/&amp;gt;&lt;br /&gt;
set_user_preference($name, $value, $otheruserid=NULL)&amp;lt;br/&amp;gt;&lt;br /&gt;
set_user_preferences($prefarray, $otheruserid=NULL)&amp;lt;br/&amp;gt;&lt;br /&gt;
truncate_userinfo($info)&amp;lt;br/&amp;gt;&lt;br /&gt;
unset_user_preference($name, $otheruserid=NULL)&amp;lt;br/&amp;gt;&lt;br /&gt;
update_internal_user_password(&amp;amp;$user, $password)&amp;lt;br/&amp;gt;&lt;br /&gt;
update_user_login_times()&amp;lt;br/&amp;gt;&lt;br /&gt;
update_user_record($username, $authplugin)&amp;lt;br/&amp;gt;&lt;br /&gt;
user_not_fully_set_up($user)&amp;lt;br/&amp;gt;&lt;br /&gt;
user_preference_allow_ajax_update($name, $paramtype)&amp;lt;br/&amp;gt;&lt;br /&gt;
userdate($date, $format = &#039;&#039;, $timezone = 99, $fixday = true)&amp;lt;br/&amp;gt;&lt;br /&gt;
usergetdate($time, $timezone=99)&amp;lt;br/&amp;gt;&lt;br /&gt;
usergetmidnight($date, $timezone=99)&amp;lt;br/&amp;gt;&lt;br /&gt;
usertime($date, $timezone=99)&amp;lt;br/&amp;gt;&lt;br /&gt;
usertimezone($timezone=99)&amp;lt;br/&amp;gt;&lt;br /&gt;
validate_internal_user_password(&amp;amp;$user, $password)&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Move to lib/deprecatedlib.php ===&lt;br /&gt;
* create_user_record($username, $password, $auth=&#039;manual&#039;)&lt;br /&gt;
* update_user_record($username, $authplugin)&lt;br /&gt;
* get_complete_user_data($field, $value, $mnethostid=null)&lt;br /&gt;
&lt;br /&gt;
=== New user/lib.php ===&lt;br /&gt;
* user_create_user($user)&lt;br /&gt;
* user_delete_user($user)&lt;br /&gt;
* user_update_user($user)&lt;br /&gt;
* user_get_user($user)&lt;br /&gt;
&lt;br /&gt;
* get_user_preferences($name=NULL, $default=NULL, $otheruserid=NULL)&lt;br /&gt;
* set_user_preference($name, $value, $otheruserid=NULL)&lt;br /&gt;
* set_user_preferences($prefarray, $otheruserid=NULL)&lt;br /&gt;
* unset_user_preference($name, $otheruserid=NULL)&lt;br /&gt;
* guest_user()&lt;br /&gt;
* isguestuser($user=NULL)&lt;br /&gt;
* isloggedin()&lt;br /&gt;
* is_restored_user($username)&lt;br /&gt;
* complete_user_login($user, $setcookie=true)&lt;br /&gt;
&lt;br /&gt;
=== New user/locallib.php ===&lt;br /&gt;
authenticate_user_login($username, $password)&amp;lt;br/&amp;gt;&lt;br /&gt;
check_user_preferences_loaded($time = null)&amp;lt;br/&amp;gt;&lt;br /&gt;
create_user_key($script, $userid, $instance=null, $iprestriction=null, $validuntil=null)&amp;lt;br/&amp;gt;&lt;br /&gt;
get_user_directories($only_non_empty=true, $legacy=false)&amp;lt;br/&amp;gt;&lt;br /&gt;
get_user_fieldnames()&amp;lt;br/&amp;gt;&lt;br /&gt;
get_users_from_config($value, $capability)&amp;lt;br/&amp;gt;&lt;br /&gt;
hash_internal_user_password($password)&amp;lt;br/&amp;gt;&lt;br /&gt;
make_user_directory($userid, $test=false)&amp;lt;br/&amp;gt;&lt;br /&gt;
mark_user_preferences_changed($userid)&amp;lt;br/&amp;gt;&lt;br /&gt;
truncate_userinfo($info)&amp;lt;br/&amp;gt;&lt;br /&gt;
update_internal_user_password(&amp;amp;$user, $password)&amp;lt;br/&amp;gt;&lt;br /&gt;
update_user_login_times()&amp;lt;br/&amp;gt;&lt;br /&gt;
user_not_fully_set_up($user)&amp;lt;br/&amp;gt;&lt;br /&gt;
user_preference_allow_ajax_update($name, $paramtype)&amp;lt;br/&amp;gt;&lt;br /&gt;
validate_internal_user_password(&amp;amp;$user, $password)&amp;lt;br/&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== New lib/timelib.php ===&lt;br /&gt;
&lt;br /&gt;
userdate($date, $format = &#039;&#039;, $timezone = 99, $fixday = true)&amp;lt;br/&amp;gt;&lt;br /&gt;
usergetdate($time, $timezone=99)&amp;lt;br/&amp;gt;&lt;br /&gt;
usergetmidnight($date, $timezone=99)&amp;lt;br/&amp;gt;&lt;br /&gt;
usertime($date, $timezone=99)&amp;lt;br/&amp;gt;&lt;br /&gt;
usertimezone($timezone=99)&amp;lt;br/&amp;gt;&lt;br /&gt;
calculate_user_dst_table($from_year = NULL, $to_year = NULL, $strtimezone = NULL)&amp;lt;br/&amp;gt;&lt;br /&gt;
get_user_timezone($tz = 99)&amp;lt;br/&amp;gt;&lt;br /&gt;
get_user_timezone_offset($tz = 99)&amp;lt;br/&amp;gt;&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=lib.php&amp;diff=53685</id>
		<title>lib.php</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=lib.php&amp;diff=53685"/>
		<updated>2018-02-09T09:33:02Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Within a module or plugin, the file &amp;lt;tt&amp;gt;lib.php&amp;lt;/tt&amp;gt; contains the main API for the plugin.&lt;br /&gt;
&lt;br /&gt;
== API functions ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;lib.php&amp;lt;/tt&amp;gt; contains a number of functions, some of which are required and some are optional, which may be called by the Moodle system.&lt;br /&gt;
&lt;br /&gt;
The name of all these functions begins with your module or plugin name, e.g. &amp;lt;tt&amp;gt;forum_supports&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;format_topics_supports&amp;lt;/tt&amp;gt;. In the list below we show only the part of the name after that underline.&lt;br /&gt;
&lt;br /&gt;
=== TODO Document all functions here ===&lt;br /&gt;
&lt;br /&gt;
These functions are not documented yet. There&#039;s a little bit more information about some of them here: [[NEWMODULE_Documentation#lib.php]].&lt;br /&gt;
&lt;br /&gt;
=== Required functions ===&lt;br /&gt;
&lt;br /&gt;
=== Optional functions ===&lt;br /&gt;
&lt;br /&gt;
==== _supports($feature) ====&lt;br /&gt;
&lt;br /&gt;
Returns metadata about the plugin. $feature is a FEATURE_xx constant and, in order to obtain non-default behaviour, you return a specific value for certain constants.&lt;br /&gt;
&lt;br /&gt;
For a full list of FEATURE_xx constants, see the code. Here are some:&lt;br /&gt;
&lt;br /&gt;
* FEATURE_BACKUP_MOODLE2 - return true if you implement Moodle 2 backup/restore system.&lt;br /&gt;
* FEATURE_MOD_ARCHETYPE (modules only) - controls how users can add the module. Default is to add using the &#039;Add activity&#039; dropdown. Return MOD_ARCHETYPE_RESOURCE to add using the &#039;Add resource&#039; dropdown, or MOD_ARCHETYPE_SYSTEM (Moodle 2.3+) if the module cannot be added by users at all but is only added programmatically by system components.&lt;br /&gt;
&lt;br /&gt;
== Other functions ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;lib.php&amp;lt;/tt&amp;gt; should not contain any functions other than the API functions (and possibly a few small supporting functions). Any other significant functions should be placed in appropriate classes within [[Automatic_class_loading#Class_files_and_locations|your plugin&#039;s &amp;lt;tt&amp;gt;classes/&amp;lt;/tt&amp;gt; directory]].&lt;br /&gt;
&lt;br /&gt;
The reason for this is that &amp;lt;tt&amp;gt;lib.php&amp;lt;/tt&amp;gt; for all modules is included on every single Moodle page, even if it is not much used. For performance reasons it is therefore important to ensure the file is as short as possible.&lt;br /&gt;
&lt;br /&gt;
== Requiring other files ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;lib.php&amp;lt;/tt&amp;gt; should not require any other files at the top level. If any of the functions within &amp;lt;tt&amp;gt;lib.php&amp;lt;/tt&amp;gt; need to refer to functions from other files, the &amp;lt;tt&amp;gt;require_once&amp;lt;/tt&amp;gt; statements should be placed within that function.&lt;br /&gt;
&lt;br /&gt;
The reason for this is that &amp;lt;tt&amp;gt;lib.php&amp;lt;/tt&amp;gt; for all modules is included on every single Moodle page, even if it is not much used. For performance reasons it is therefore important to avoid requiring any other files as a consequence.&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium&amp;diff=52205</id>
		<title>Acceptance testing/Browsers/Working combinations of OS+Browser+selenium</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium&amp;diff=52205"/>
		<updated>2017-05-02T10:01:56Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Working combinations of OS+Browser+selenium =&lt;br /&gt;
As OS, Browsers and Selenium keeps updating, some combination might fail on different Moodle releases.&lt;br /&gt;
&lt;br /&gt;
Following combinations have been tested at the time of release of Moodle version and will be supported for that combination. &lt;br /&gt;
NOTE:  Chromedriver &amp;gt;= 2.17 is required for behat to capture exceptions&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.2 and master ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar 2.53.1]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 16.04&lt;br /&gt;
|[https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F403380%2Fchrome-linux.zip?generation=1467337264475000&amp;amp;alt=media Chrome 53.0]&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/index.html?path=3.0/ 3.0.1]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.25/ 2.25]&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Phantomjs 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Chrome v53.0&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/index.html?path=3.0/ 3.0.1]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.25/ 2.25]&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| [http://www.slimjet.com/chrome/google-chrome-old-version.php Chrome v53.0]&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/index.html?path=3.0/ 3.0.1]&lt;br /&gt;
| [http://chromedriver.storage.googleapis.com/index.html?path=2.25/ 2.25]&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| PhantomJS 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.1 ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| [https://download.mozilla.org/?product=firefox-47.0.1-SSL&amp;amp;os=linux64&amp;amp;lang=en-GB Firefox 47.0.1]&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar 2.53.1]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 16.04&lt;br /&gt;
|[https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F386249%2Fchrome-linux.zip?generation=1460160957434000&amp;amp;alt=media Chrome 51.0]&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Phantomjs 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Chrome v51.0&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Chrome v51.0&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| PhantomJS 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.0 and lower ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Firefox 42.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Chrome 46.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.19.346067&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Phantomjs 2.0.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Windows 7/10&lt;br /&gt;
|Firefox 41.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Windows 7/10&lt;br /&gt;
|Chrome 47.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.20&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Firefox 41.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Chrome 46.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.20&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Chrome 48.0&lt;br /&gt;
|2.51.0&lt;br /&gt;
| 2.21&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|PhantomJS - 2.0.0 &amp;amp; 2.1.1&lt;br /&gt;
|2.48.2&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
[[Category:Behat]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User:Mark_Johnson/Combined_javascript_style&amp;diff=51590</id>
		<title>User:Mark Johnson/Combined javascript style</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User:Mark_Johnson/Combined_javascript_style&amp;diff=51590"/>
		<updated>2016-12-13T13:27:31Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Created page with &amp;quot; ==File Formatting==  ===Indentation===  Use an indent of &amp;#039;&amp;#039;&amp;#039;4 spaces&amp;#039;&amp;#039;&amp;#039; with no tab characters. Editors should be configured to treat tabs as spaces in order to prevent injec...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
==File Formatting==&lt;br /&gt;
&lt;br /&gt;
===Indentation===&lt;br /&gt;
&lt;br /&gt;
Use an indent of &#039;&#039;&#039;4 spaces&#039;&#039;&#039; with no tab characters. Editors should be configured to treat tabs as spaces in order to prevent injection of new tab characters into the source code.&lt;br /&gt;
&lt;br /&gt;
Don&#039;t indent the main script level:&lt;br /&gt;
&lt;br /&gt;
GOOD:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
require(&#039;config.php&#039;);&lt;br /&gt;
$a = required_param(&#039;a&#039;, PARAM_INT);&lt;br /&gt;
if ($a &amp;gt; 10) {&lt;br /&gt;
    call_some_error($a);&lt;br /&gt;
} else {&lt;br /&gt;
    do_something_with($a);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
BAD:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
    require(&#039;config.php&#039;);&lt;br /&gt;
    $a = required_param(&#039;a&#039;, PARAM_INT);&lt;br /&gt;
    if ($a &amp;gt; 10) {&lt;br /&gt;
        call_some_error($a);&lt;br /&gt;
    } else {&lt;br /&gt;
        do_something_with($a);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
SQL queries use special indentation, see [[SQL coding style]].&lt;br /&gt;
&lt;br /&gt;
===Maximum Line Length===&lt;br /&gt;
&lt;br /&gt;
The key issue is readability.&lt;br /&gt;
&lt;br /&gt;
Aim for 132 characters if it is convenient,  it is not recommended to use more than 180 characters.&lt;br /&gt;
&lt;br /&gt;
The exception are string files in the &amp;lt;tt&amp;gt;/lang&amp;lt;/tt&amp;gt; directory where lines &amp;lt;tt&amp;gt;$string[&#039;id&#039;] = &#039;value&#039;;&amp;lt;/tt&amp;gt; should have the value defined as a single string of any length, wrapped by quotes (no concatenation operators, no heredoc and no newdoc syntax). This helps to parse and process these string files without including them as a PHP code.&lt;br /&gt;
&lt;br /&gt;
====Wrapping lines====&lt;br /&gt;
&lt;br /&gt;
When wrapping a line, indent the follow-on line by 8 spaces rather than 4. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (a_long_condition() &amp;amp;&amp;amp;&lt;br /&gt;
        a_nother_long_condition()) {&lt;br /&gt;
    do_something();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using eight spaces makes it easier to spot the difference between wrapped lines and indented blocks.&lt;br /&gt;
&lt;br /&gt;
The example above is only an illustration; it is best practice to avoid wrapping lines in control structures. See below.&lt;br /&gt;
&lt;br /&gt;
====Wrapping Arrays====&lt;br /&gt;
&lt;br /&gt;
Associative arrays are a exception to the rule about 8-space indent for follow-on lines. The correct layout is:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$plugininfo[&#039;preferences&#039;][$plugin] = array(&lt;br /&gt;
    &#039;id&#039;     =&amp;gt; $plugin, &lt;br /&gt;
    &#039;link&#039;   =&amp;gt; $pref_url, &lt;br /&gt;
    &#039;string&#039; =&amp;gt; $modulenamestr&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Lining up the =&amp;gt;s is optional. Small arrays can be done on one line.&lt;br /&gt;
&lt;br /&gt;
====Wrapping function declarations====&lt;br /&gt;
If you have many parameters, indent them in line with the first parameter:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function graded_users_iterator($course, $grade_items = null, $groupid = 0,&lt;br /&gt;
                                      $sortfield1 = &#039;lastname&#039;, $sortorder1 = &#039;ASC&#039;,&lt;br /&gt;
                                      $sortfield2 = &#039;firstname&#039;, $sortorder2 = &#039;ASC&#039;) {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Wrapping Control Structures====&lt;br /&gt;
If you have too many conditions in one control structure, try setting some variables before the start of the structure to improve readability.&lt;br /&gt;
&lt;br /&gt;
GOOD:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$coursecategory = ($element[&#039;object&#039;]-&amp;gt;is_course_item() or $element[&#039;object&#039;]-&amp;gt;is_category_item());&lt;br /&gt;
$scalevalue = in_array($element[&#039;object&#039;]-&amp;gt;gradetype, array(GRADE_TYPE_SCALE, GRADE_TYPE_VALUE));&lt;br /&gt;
&lt;br /&gt;
if ($coursecategory and $scalevalue) {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
BAD:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (($element[&#039;object&#039;]-&amp;gt;is_course_item() or $element[&#039;object&#039;]-&amp;gt;is_category_item())&lt;br /&gt;
    and ($element[&#039;object&#039;]-&amp;gt;gradetype == GRADE_TYPE_SCALE&lt;br /&gt;
    or $element[&#039;object&#039;]-&amp;gt;gradetype == GRADE_TYPE_VALUE)) {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Method call wrapping ===&lt;br /&gt;
&lt;br /&gt;
When wrapping a long line which consists of a chained series of functions,&lt;br /&gt;
break the line at the end of each function, and continue the next chain on&lt;br /&gt;
a new line.&lt;br /&gt;
&lt;br /&gt;
The line should be indented by  spaces.&lt;br /&gt;
&lt;br /&gt;
The start of each line should contain the concatanation character, and the&lt;br /&gt;
final line should contain a trailing semicolon.&lt;br /&gt;
&lt;br /&gt;
==== Correct ====&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
var childNode = Y.Node.create(&#039;&amp;lt;div /&amp;gt;&#039;)&lt;br /&gt;
        .addClass(CSS.SOMECLASS)&lt;br /&gt;
        .setAttribute(&#039;someAttribute&#039;, &#039;someValue&#039;)&lt;br /&gt;
        .appendTo(parentNode);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Incorrect ====&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// All on one line:&lt;br /&gt;
var childNode = Y.Node.create(&#039;&amp;lt;div /&amp;gt;&#039;).addClass(CSS.SOMECLASS).setAttribute(&#039;someAttribute&#039;, &#039;someValue&#039;).appendTo(parentNode);&lt;br /&gt;
&lt;br /&gt;
// A mix of separation and line concatanation:&lt;br /&gt;
var childNode = Y.Node.create(&#039;&amp;lt;div /&amp;gt;&#039;).addClass(CSS.SOMECLASS)&lt;br /&gt;
        .setAttribute(&#039;someAttribute&#039;, &#039;someValue&#039;).appendTo(parentNode);&lt;br /&gt;
&lt;br /&gt;
// The concatanation character is at the end of the line:&lt;br /&gt;
var childNode = Y.Node.create(&#039;&amp;lt;div /&amp;gt;&#039;).&lt;br /&gt;
        addClass(CSS.SOMECLASS).&lt;br /&gt;
        setAttribute(&#039;someAttribute&#039;, &#039;someValue&#039;).&lt;br /&gt;
        appendTo(parentNode);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Line Termination===&lt;br /&gt;
&lt;br /&gt;
Use standard &#039;&#039;&#039;Unix&#039;&#039;&#039; text format. Lines must end only with a linefeed (LF). Linefeeds are represented as ordinal 10, or hexadecimal 0x0A.&lt;br /&gt;
&lt;br /&gt;
Do not use carriage returns (CR) like old &#039;&#039;Macintosh&#039;&#039; computers (0x0D).&lt;br /&gt;
&lt;br /&gt;
Do not use the carriage return/linefeed combination (CRLF) as &#039;&#039;Windows&#039;&#039; computers (0x0D, 0x0A).&lt;br /&gt;
&lt;br /&gt;
Lines should not contain trailing spaces. In order to facilitate this convention, most editors can be configured to strip trailing spaces, such as upon a save operation. However, if you are editing an existing Moodle file and are planning to submit your changes for integration, please switch off that feature so that whitespace changes do not pollute the patch (other developers will have trouble viewing what you&#039;ve done if every other line has been edited for whitespace).&lt;br /&gt;
&lt;br /&gt;
==Naming Conventions==&lt;br /&gt;
&lt;br /&gt;
===Filenames===&lt;br /&gt;
&lt;br /&gt;
Filenames should :&lt;br /&gt;
* be whole english words&lt;br /&gt;
* be as short as possible&lt;br /&gt;
* use lowercase letters only &lt;br /&gt;
* end in .php, .html, .js, .css or .xml&lt;br /&gt;
&lt;br /&gt;
=== Class naming ===&lt;br /&gt;
&lt;br /&gt;
Classes should be named using CamelCase starting with an uppercase&lt;br /&gt;
letter.&lt;br /&gt;
&lt;br /&gt;
This helps to clearly separate variables, and standard functions from those&lt;br /&gt;
used to create a new instance.&lt;br /&gt;
&lt;br /&gt;
==== Correct ====&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// The instantiator:&lt;br /&gt;
function Pantry() {&lt;br /&gt;
    // Setup code goes here.&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Making use of it:&lt;br /&gt;
var myPantry = new Pantry();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
// And another:&lt;br /&gt;
function PantryShelf() {&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var myPantryShelf = new PantryShelf();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Incorrect ====&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// There is no distinction here between a normal function, and one used to&lt;br /&gt;
// create a new object:&lt;br /&gt;
function pantry() {&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// This results in an unclear object creation:&lt;br /&gt;
var myPantry = new pantry();&lt;br /&gt;
&lt;br /&gt;
// This one is also incorrect, despite using camelCase:&lt;br /&gt;
function pantryShelf() {&lt;br /&gt;
}&lt;br /&gt;
var myPantryShelf = new pantryShelf();&lt;br /&gt;
&lt;br /&gt;
// This one is also incorrect:&lt;br /&gt;
function pantry_shelf() {&lt;br /&gt;
}&lt;br /&gt;
var myPantryShelf = new pantry_shelf();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Variable and function naming ===&lt;br /&gt;
&lt;br /&gt;
Contrary to the standard Moodle coding style, camelCase should be used to name variables and functions in JavaScript.&lt;br /&gt;
&lt;br /&gt;
==== Correct ====&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
var currentY,&lt;br /&gt;
    courseCategory,&lt;br /&gt;
    lastValue,&lt;br /&gt;
    lastBackgroundColor;&lt;br /&gt;
&lt;br /&gt;
function doSomething() {&lt;br /&gt;
    // Do stuff here.&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function doSomethingElse() {&lt;br /&gt;
    // Do stuff here.&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var someFunction = function() {&lt;br /&gt;
    // Do stuff here.&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Incorrect ====&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
var current_y,&lt;br /&gt;
    currenty,&lt;br /&gt;
    course_category,&lt;br /&gt;
    coursecategory,&lt;br /&gt;
    last_value,&lt;br /&gt;
    lastvalue,&lt;br /&gt;
    last_background_color,&lt;br /&gt;
    lastbackgroundcolor;&lt;br /&gt;
&lt;br /&gt;
function dosomething() {&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function do_something_else() {&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
var somefunction = function() {&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var some_other_function = function() {&lt;br /&gt;
};&lt;br /&gt;
&lt;br /&gt;
var somevalue = null;&lt;br /&gt;
&lt;br /&gt;
if (someTest) {&lt;br /&gt;
    somevalue = function() {&lt;br /&gt;
        return (something &amp;amp;&amp;amp; complicated || somethingelse);&lt;br /&gt;
    };&lt;br /&gt;
} else {&lt;br /&gt;
    somevalue = &#039;basicvalue&#039;;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Function Parameters====&lt;br /&gt;
&lt;br /&gt;
Parameters are always simple lowercase English words (sometimes more than one, like $initialvalue), and should always have sensible defaults if possible. &lt;br /&gt;
&lt;br /&gt;
Use &amp;quot;null&amp;quot; as the default value instead of &amp;quot;false&amp;quot; for situations like this where a default value isn&#039;t needed.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function foo($required, $optional = null)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
However, if an optional parameter is boolean, and its logical default value should be true, or false, then using true or false is acceptable.&lt;br /&gt;
&lt;br /&gt;
=== Constants ===&lt;br /&gt;
&lt;br /&gt;
Variables intended to be constants should use the same naming style of ALL UPPERCASE.&lt;br /&gt;
&lt;br /&gt;
Constants are recommended for use in the following scenarios:&lt;br /&gt;
* CSS: An object containing any CSS classes you may wish to use with Nodes; and&lt;br /&gt;
* SELECTORS: An object containing query selectors for selecting Nodes.&lt;br /&gt;
&lt;br /&gt;
===Booleans and the null value===&lt;br /&gt;
&lt;br /&gt;
Use lower case for &#039;&#039;&#039;true&#039;&#039;&#039;, &#039;&#039;&#039;false&#039;&#039;&#039; and &#039;&#039;&#039;null&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
== Variables ==&lt;br /&gt;
&lt;br /&gt;
All variables must:&lt;br /&gt;
* be declared before they are used and using the &amp;lt;tt&amp;gt;var&amp;lt;/tt&amp;gt; keyword;&lt;br /&gt;
* be declared once, and only once, for the scope in which they are used;&lt;br /&gt;
* only be declared if they are to be used; and&lt;br /&gt;
* use sensible naming, following the naming convention.&lt;br /&gt;
&lt;br /&gt;
== Strings ==&lt;br /&gt;
&lt;br /&gt;
Since string performance is not an issue in current versions of PHP, the main criteria for strings is readability. &lt;br /&gt;
&lt;br /&gt;
===Single quotes===&lt;br /&gt;
&lt;br /&gt;
Always use single quotes when a string is literal, or contains a lot of double quotes (like HTML):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$a = &#039;Example string&#039;; &lt;br /&gt;
echo &#039;&amp;lt;span class=&amp;quot;&#039;.s($class).&#039;&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&#039;; &lt;br /&gt;
$html = &#039;&amp;lt;a href=&amp;quot;http://something&amp;quot; title=&amp;quot;something&amp;quot;&amp;gt;Link&amp;lt;/a&amp;gt;&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Double quotes===&lt;br /&gt;
&lt;br /&gt;
These are a lot less useful in Moodle. Use double quotes when you need to include plain variables or a lot of single quotes. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
echo &amp;quot;&amp;lt;span&amp;gt;$string&amp;lt;/span&amp;gt;&amp;quot;; &lt;br /&gt;
$statement = &amp;quot;You aren&#039;t serious!&amp;quot;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Complex SQL queries should be always enclosed in double quotes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$sql = &amp;quot;SELECT e.*, ue.userid&lt;br /&gt;
          FROM {user_enrolments} ue&lt;br /&gt;
          JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = &#039;self&#039; AND e.customint2 &amp;gt; 0)&lt;br /&gt;
          JOIN {user} u ON u.id = ue.userid&lt;br /&gt;
         WHERE :now - u.lastaccess &amp;gt; e.customint2&amp;quot;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Variable substitution===&lt;br /&gt;
&lt;br /&gt;
Variable substitution can use either of these forms:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$greeting = &amp;quot;Hello $name, welcome back!&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
$greeting = &amp;quot;Hello {$name}, welcome back!&amp;quot;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===String concatenation===&lt;br /&gt;
&lt;br /&gt;
Strings must be concatenated using the &amp;quot;.&amp;quot; operator.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$longstring = $several.$short.&#039;strings&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the lines are long, break the statement into multiple lines to improve readability.  In these cases, put the &amp;quot;dot&amp;quot; at the end of each line.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string = &#039;This is a very long and stupid string because &#039;.$editorname.&lt;br /&gt;
          &amp;quot; couldn&#039;t think of a better example at the time.&amp;quot;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The dot operator may be used without any space to either side (as shown in the above examples), or with spaces on each side; whichever the developer prefers.&lt;br /&gt;
&lt;br /&gt;
==Arrays==&lt;br /&gt;
&lt;br /&gt;
===Numerically indexed arrays===&lt;br /&gt;
&lt;br /&gt;
When declaring indexed arrays with the array function, a trailing space must be added after each comma delimiter to improve readability:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$myarray = array(1, 2, 3, &#039;Stuff&#039;, &#039;Here&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Multi-line indexed arrays are fine, but pad each successive lines as above with an 8-space indent:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$myarray = array(&lt;br /&gt;
        1, 2, 3, &#039;Stuff&#039;, &#039;Here&#039;,&lt;br /&gt;
        $a, $b, $c, 56.44, $d, 500);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Associative arrays===&lt;br /&gt;
&lt;br /&gt;
Use multiple lines if this helps readability. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$myarray = array(&lt;br /&gt;
    &#039;firstkey&#039; =&amp;gt; &#039;firstvalue&#039;,&lt;br /&gt;
    &#039;secondkey&#039; =&amp;gt; &#039;secondvalue&#039;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Classes==&lt;br /&gt;
&lt;br /&gt;
=== Class declarations ===&lt;br /&gt;
&lt;br /&gt;
* Classes must be named according to Moodle&#039;s naming conventions.&lt;br /&gt;
* The brace should always be written on the line beside the class name.&lt;br /&gt;
* Every class must have a documentation block that conforms to the PHPDocumentor standard.&lt;br /&gt;
* All code in a class must be indented with 4 spaces.&lt;br /&gt;
* Placing additional code in class files is permitted but discouraged. In such files, two blank lines must separate the class from any additional PHP code in the class file.&lt;br /&gt;
&lt;br /&gt;
: An example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Documentation Block Here&lt;br /&gt;
 */&lt;br /&gt;
class sample_class {&lt;br /&gt;
    // All contents of class&lt;br /&gt;
    // must be indented 4 spaces.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Class member variables===&lt;br /&gt;
&lt;br /&gt;
In the case of object property assignment, there should be a space after&lt;br /&gt;
the colon, but not before.&lt;br /&gt;
&lt;br /&gt;
==== Correct ====&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
var anObject = {&lt;br /&gt;
        someKey: &#039;someValue&#039;,&lt;br /&gt;
        anotherKey: Y.one(SELECTORS.FOO)&lt;br /&gt;
    };&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Incorrect ====&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
var anObject = {&lt;br /&gt;
        // Incorrect because a space is present both before and after the assignation character:&lt;br /&gt;
        someKey : &#039;someValue&#039;,&lt;br /&gt;
&lt;br /&gt;
        // Incorrect because there is no whitespace either side of the assignation character:&lt;br /&gt;
        anotherKey:Y.one(SELECTORS.FOO)&lt;br /&gt;
    };&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Functions and methods==&lt;br /&gt;
===Function and method declaration===&lt;br /&gt;
&lt;br /&gt;
Functions must be named according to the Moodle function naming conventions.&lt;br /&gt;
&lt;br /&gt;
Methods inside classes must always declare their visibility by using one of the private, protected, or public modifiers.&lt;br /&gt;
&lt;br /&gt;
As with classes, the brace should always be written on same line as the function name. &lt;br /&gt;
&lt;br /&gt;
Don&#039;t leave spaces between the function name and the opening parenthesis for the arguments.&lt;br /&gt;
&lt;br /&gt;
The return value must not be enclosed in parentheses. This can hinder readability, in additional to breaking code if a method is later changed to return by reference. &lt;br /&gt;
&lt;br /&gt;
Return should only be one data type. It is discouraged to have multiple return types&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Documentation Block Here&lt;br /&gt;
 */&lt;br /&gt;
class sample_class {&lt;br /&gt;
    &lt;br /&gt;
    /**&lt;br /&gt;
     * Documentation Block Here&lt;br /&gt;
     */&lt;br /&gt;
    public function sample_function() {&lt;br /&gt;
        // All contents of function&lt;br /&gt;
        // must be indented four spaces.&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Function and method usage===&lt;br /&gt;
&lt;br /&gt;
Function arguments should be separated by a single trailing space after the comma delimiter. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
three_arguments(1, 2, 3);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Control statements==&lt;br /&gt;
&lt;br /&gt;
In general, use white space liberally between lines and so on, to add clarity.&lt;br /&gt;
&lt;br /&gt;
===If / else===&lt;br /&gt;
&lt;br /&gt;
Put a space before and after the control statement in brackets, and separate the operators by spaces within the brackets.  Use inner brackets to improve logical grouping if it helps. &lt;br /&gt;
&lt;br /&gt;
Indent with four spaces. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;Don&#039;t use elseif!&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Always use braces (even if the block is one line and PHP doesn&#039;t require it).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($x == $y) {&lt;br /&gt;
    $a = $b;&lt;br /&gt;
} else if ($x == $z) {&lt;br /&gt;
    $a = $c;&lt;br /&gt;
} else {&lt;br /&gt;
    $a = $d;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Switch===&lt;br /&gt;
&lt;br /&gt;
Put a space before and after the control statement in brackets, and separate the operators by spaces within the brackets.  Use inner brackets to improve logical grouping if it helps. &lt;br /&gt;
&lt;br /&gt;
Always indent with four spaces.  Content under each case statement should be indented a further four spaces.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
switch ($something) {&lt;br /&gt;
    case 1:&lt;br /&gt;
        break;&lt;br /&gt;
&lt;br /&gt;
    case 2:&lt;br /&gt;
        break;&lt;br /&gt;
&lt;br /&gt;
    default:&lt;br /&gt;
        break;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Foreach===&lt;br /&gt;
&lt;br /&gt;
As above, uses spaces like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
foreach ($objects as $key =&amp;gt; $thing) {&lt;br /&gt;
    process($thing);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Ternary Operator===&lt;br /&gt;
&lt;br /&gt;
The ternary operator is only permitted to be used for &#039;&#039;&#039;short&#039;&#039;&#039;, &#039;&#039;&#039;simple to understand&#039;&#039;&#039; statements. If the statement can&#039;t be understood in one sentance, use an if statement instead.&lt;br /&gt;
&lt;br /&gt;
Whitespace must be used around the operators to make it clear where the operation is taking place.&lt;br /&gt;
&lt;br /&gt;
GOOD:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$username = isset($user-&amp;gt;username) ? $user-&amp;gt;username : &#039;&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
BAD:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$toload = (empty($CFG-&amp;gt;navshowallcourses))?self::LOAD_ROOT_CATEGORIES:self::LOAD_ALL_CATEGORIES;&lt;br /&gt;
$coefstring = ($coefstring==&#039;&#039; or $coefstring==&#039;aggregationcoefextrasum&#039;) ? &#039;aggregationcoefextrasum&#039; : &#039;aggregationcoef&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Operators ===&lt;br /&gt;
&lt;br /&gt;
There should be a space at either side of &#039;&#039;&#039;all&#039;&#039;&#039; binary operators to help improve&lt;br /&gt;
legibility of code. This includes:&lt;br /&gt;
* =&lt;br /&gt;
* &amp;amp;&amp;amp;&lt;br /&gt;
* ||&lt;br /&gt;
* ===&lt;br /&gt;
* +&lt;br /&gt;
* -&lt;br /&gt;
* /&lt;br /&gt;
* *&lt;br /&gt;
&lt;br /&gt;
There should be no space around unary operators. This includes:&lt;br /&gt;
* !&lt;br /&gt;
* ++&lt;br /&gt;
* --&lt;br /&gt;
&lt;br /&gt;
There should be no space around the function operator (.)&lt;br /&gt;
&lt;br /&gt;
==== Correct ====&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Valid binary operators:&lt;br /&gt;
var a = 1,&lt;br /&gt;
    b = (a &amp;amp;&amp;amp; 1),&lt;br /&gt;
    c = (b || 1),&lt;br /&gt;
    d = (b === c),&lt;br /&gt;
    e = Y.Node.create(&#039;&amp;lt;div&amp;gt;Some Content&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
&lt;br /&gt;
// No space around the . operator when it&#039;s not a continuation:&lt;br /&gt;
e.someFunctionCall();&lt;br /&gt;
&lt;br /&gt;
// Whitespace is allowed for a function operator when it is a continuation starting on a new line:&lt;br /&gt;
e.someFunction()&lt;br /&gt;
    .someOtherFunction()&lt;br /&gt;
    .someFinalFunction();&lt;br /&gt;
&lt;br /&gt;
// Unary operators should not be separated by whitespace:&lt;br /&gt;
a = a++;&lt;br /&gt;
b = b--;&lt;br /&gt;
c = (!e.someResult());&lt;br /&gt;
&lt;br /&gt;
// An example bringing most of these together:&lt;br /&gt;
var index,&lt;br /&gt;
    loopTest = 0;&lt;br /&gt;
for (index = 0; (!loopTest &amp;lt;= (a / b * (c + d - e.getValue()))); index++) {&lt;br /&gt;
    loopTest = index * 12;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Incorrect ====&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
var a=1,&lt;br /&gt;
    b= (a&amp;amp;&amp;amp;1),&lt;br /&gt;
    c =(b||1),&lt;br /&gt;
    d = (b===c);&lt;br /&gt;
&lt;br /&gt;
a = a ++;&lt;br /&gt;
b =b++;&lt;br /&gt;
c= c++;&lt;br /&gt;
d = d++ ;&lt;br /&gt;
&lt;br /&gt;
var e = Y . Node . create(&#039;&amp;lt;div&amp;gt;Some content&amp;lt;/div&amp;gt;&#039;);&lt;br /&gt;
&lt;br /&gt;
for ( index = 0;index&amp;lt;a; index ++ ) {&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Documentation and comments ==&lt;br /&gt;
&lt;br /&gt;
Modules should be documented using the standard YUI guidance: http://yui.github.io/yuidoc/syntax/index.html.&lt;br /&gt;
&lt;br /&gt;
=== General notes ===&lt;br /&gt;
&lt;br /&gt;
* Unless otherwise specified, comments should conform to the general style guidelines;&lt;br /&gt;
* all comments must start with leading whitespace before the first word on each line; and&lt;br /&gt;
* all indentation must be in addition to any existing leading whitespace on the line.&lt;br /&gt;
&lt;br /&gt;
=== Official documentation ===&lt;br /&gt;
&lt;br /&gt;
All JavaScript documentation must:&lt;br /&gt;
* use the correct docblock format;&lt;br /&gt;
* use the correct JavaScript types where relevant (note, Int is not a valid type in JavaScript);&lt;br /&gt;
* use all appropriate tags;&lt;br /&gt;
* produce valid documentation using the YUIDoc toolset;&lt;br /&gt;
* have a linebreak between the description and the list of tags.&lt;br /&gt;
&lt;br /&gt;
Note:&lt;br /&gt;
&lt;br /&gt;
YUIDoc will only generate documentation for docblocks starting with /**.&lt;br /&gt;
&lt;br /&gt;
YUIDoc will try to generate documentation for *all* docblocks starting /**.&lt;br /&gt;
&lt;br /&gt;
==== Correct ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * This docblock describes a YUI module.&lt;br /&gt;
 *&lt;br /&gt;
 * @module moodle-mod_food-marmite&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * This docblock describes the marmite class within the&lt;br /&gt;
 * moodle-mod_food-marmite module.&lt;br /&gt;
 *&lt;br /&gt;
 * @class Marmite&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * This is an example docblock comment. It describes a function called&lt;br /&gt;
 * marmite.&lt;br /&gt;
 *&lt;br /&gt;
 * It adds a number of jars of marmite to the cupboard.&lt;br /&gt;
 *&lt;br /&gt;
 * @method addMarmite&lt;br /&gt;
 * @param {Number} [jarCount=1] The number of jars of marmite to add to the&lt;br /&gt;
 * cupboard. This parameter is optional and defaults to 1.&lt;br /&gt;
 * @chainable&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * This docblock describes the property weight, in grams.&lt;br /&gt;
 *&lt;br /&gt;
 * @property weight&lt;br /&gt;
 * @type {Number}&lt;br /&gt;
 * @default &#039;500&#039;&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * This docblock describes an attribute.&lt;br /&gt;
 *&lt;br /&gt;
 * @attribute weight&lt;br /&gt;
 * @type {Number}&lt;br /&gt;
 * @default &#039;500&#039;&lt;br /&gt;
 */&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Incorrect ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
 * This is an invalid comment block. It wouldn&#039;t be picked up by yuidoc as&lt;br /&gt;
 * the comment style is incorrect.&lt;br /&gt;
 *&lt;br /&gt;
 * @method foo&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
// This is also an invalid comment block and wouldn&#039;t be picked up by&lt;br /&gt;
// YUIDoc.&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
* Although this style would be picked up by YUIDoc, it is hard to read.&lt;br /&gt;
*&lt;br /&gt;
* @method foo&lt;br /&gt;
*/&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 *Although this style would be picked up by YUIDoc, it is also hard to read.&lt;br /&gt;
 *&lt;br /&gt;
 *@method foo&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * This docblock is mostly valid but does not include a linebreak between&lt;br /&gt;
 * the description, and the tags.&lt;br /&gt;
 * @method foo&lt;br /&gt;
 */&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== General comments ===&lt;br /&gt;
&lt;br /&gt;
All shorter comments, for example those explaining the subsequent few lines of code should use the // style of comments.&lt;br /&gt;
&lt;br /&gt;
Comments not intended for official documentation must *not* use the Docblock style of commenting as YUIDoc will attempt to include the comment in official documentation.&lt;br /&gt;
&lt;br /&gt;
==== Correct ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// This is a valid set of comments for one line.&lt;br /&gt;
&lt;br /&gt;
// And this is a valid longer comment to describe the subsequent few lines&lt;br /&gt;
// in as much detail as required. It can consist of multiple sentences, as&lt;br /&gt;
// long as each new line starts with the correct comment style.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Incorrect ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
/* This is an invalid comment style for short comments. */&lt;br /&gt;
&lt;br /&gt;
//This is also an invalid style as there is no leading whitespace after the&lt;br /&gt;
//comment indicator.&lt;br /&gt;
&lt;br /&gt;
/**&lt;br /&gt;
 * This is an invalid multi-line comment. Multi-line comments should not&lt;br /&gt;
 * use the docblock style comments unless they are a valid and fully&lt;br /&gt;
 * formatted docblock.&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
 * This is an also invalid multi-line comment. Although it is not a full&lt;br /&gt;
 * docblock style, it does not start with the // style of comment&lt;br /&gt;
 * indicator.&lt;br /&gt;
 */&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User:Mark_Johnson&amp;diff=51589</id>
		<title>User:Mark Johnson</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User:Mark_Johnson&amp;diff=51589"/>
		<updated>2016-12-13T13:12:36Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* Mark Johnson&amp;#039;s pages */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Mark Johnson&#039;s pages ===&lt;br /&gt;
Notes, docs, etc.&lt;br /&gt;
&lt;br /&gt;
* [[User:Mark_Johnson/Namespaces]]&lt;br /&gt;
* [[User:Mark_Johnson/Output_Components_Cheatsheet]]&lt;br /&gt;
* [[User:Mark_Johnson/Javascript]]&lt;br /&gt;
* [[User:Mark_Johnson/Mforms_and_AJAX]]&lt;br /&gt;
* [[User:Mark_Johnson/Combined_javascript_style]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium&amp;diff=50750</id>
		<title>Acceptance testing/Browsers/Working combinations of OS+Browser+selenium</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Acceptance_testing/Browsers/Working_combinations_of_OS%2BBrowser%2Bselenium&amp;diff=50750"/>
		<updated>2016-08-05T15:05:44Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Added download links for working Selenium and Chromium versions&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Working combinations of OS+Browser+selenium =&lt;br /&gt;
As OS, Browsers and Selenium keeps updating, some combination might fail on different Moodle releases.&lt;br /&gt;
&lt;br /&gt;
Following combinations have been tested at the time of release of Moodle version and will be supported for that combination. &lt;br /&gt;
NOTE:  Chromedriver &amp;gt;= 2.17 is required for behat to capture exceptions&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.1 and Master ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| [http://selenium-release.storage.googleapis.com/2.53/selenium-server-standalone-2.53.1.jar 2.53.1]&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 16.04&lt;br /&gt;
|[https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F386249%2Fchrome-linux.zip?generation=1460160957434000&amp;amp;alt=media Chrome 51.0]&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Linux - Ubuntu 16.04&lt;br /&gt;
| Phantomjs 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| Windows 7/10&lt;br /&gt;
| Chrome v51.0&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Firefox 47.0.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| Chrome v51.0&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| 2.22&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
| MacOS X&lt;br /&gt;
| PhantomJS 2.1.1&lt;br /&gt;
| 2.53.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Moodle 3.0 and lower ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|OS&lt;br /&gt;
|Browser&lt;br /&gt;
|Selenium Server&lt;br /&gt;
|Chrome Driver&lt;br /&gt;
|IE Driver&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Firefox 42.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Chrome 46.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.19.346067&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Linux - Ubuntu 14.10&lt;br /&gt;
|Phantomjs 2.0.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Windows 7/10&lt;br /&gt;
|Firefox 41.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|Windows 7/10&lt;br /&gt;
|Chrome 47.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.20&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Firefox 41.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Chrome 46.0&lt;br /&gt;
|2.47.1&lt;br /&gt;
| 2.20&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|Chrome 48.0&lt;br /&gt;
|2.51.0&lt;br /&gt;
| 2.21&lt;br /&gt;
| N/A&lt;br /&gt;
|-&lt;br /&gt;
|MacOS X&lt;br /&gt;
|PhantomJS - 2.0.0 &amp;amp; 2.1.1&lt;br /&gt;
|2.48.2&lt;br /&gt;
| N/A&lt;br /&gt;
| N/A&lt;br /&gt;
|}&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Acceptance_testing/Migrating_from_Behat_2.5_to_3.x_in_Moodle&amp;diff=50413</id>
		<title>Acceptance testing/Migrating from Behat 2.5 to 3.x in Moodle</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Acceptance_testing/Migrating_from_Behat_2.5_to_3.x_in_Moodle&amp;diff=50413"/>
		<updated>2016-06-14T14:44:36Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Fixed formatting for code blocks&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Migrating from Behat 2.5 to 3.x in Moodle ==&lt;br /&gt;
&lt;br /&gt;
[http://docs.behat.org/en/v3.0/ Behat 3] brings a lot of extensibility and modularity  for the price of backward compatibility break. This guide describes several issues you might face when updating your plugin acceptance test to the latest Moodle 3.1@dev version.&lt;br /&gt;
&lt;br /&gt;
=== Config variables ===&lt;br /&gt;
====behat_profiles====&lt;br /&gt;
Most of the configuration for a behat profile can be set in $CFG-&amp;gt;behat_profiles. This configuration parameter is available since Moodle 2.9.6, Moodle 3.0.4, and Moodle 3.1.&lt;br /&gt;
&lt;br /&gt;
Here is a quick example that changes the browser from the default firefox to chrome on Linux systems (note that you have to have the [http://www.seleniumhq.org/download/ Chrome web driver installed] in your system path)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_profiles = [&lt;br /&gt;
    &#039;default&#039; =&amp;gt; [&lt;br /&gt;
        &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
        &#039;extensions&#039; =&amp;gt; [&lt;br /&gt;
            &#039;Behat\MinkExtension&#039; =&amp;gt; [&lt;br /&gt;
                &#039;selenium2&#039; =&amp;gt; [&lt;br /&gt;
                    &#039;browser&#039; =&amp;gt; &#039;chrome&#039;,&lt;br /&gt;
                ]&lt;br /&gt;
            ]&lt;br /&gt;
        ]&lt;br /&gt;
    ]&lt;br /&gt;
];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The settings available here include the browser name, tags, webdriver configuration, and capabilities:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_profiles = array(&lt;br /&gt;
   &#039;phantomjs&#039; =&amp;gt; array(&lt;br /&gt;
       &#039;browser&#039; =&amp;gt; &#039;phantomjs&#039;,&lt;br /&gt;
       &#039;tags&#039; =&amp;gt; &#039;~@_file_upload&amp;amp;&amp;amp;~@_alert&amp;amp;&amp;amp;~@_bug_phantomjs&#039;,&lt;br /&gt;
       &#039;wd_host&#039; =&amp;gt; &#039;http://127.0.0.1:4443/wd/hub&#039;,&lt;br /&gt;
       &#039;capabilities&#039; =&amp;gt; array(&lt;br /&gt;
           &#039;platform&#039; =&amp;gt; &#039;Linux&#039;,&lt;br /&gt;
           &#039;version&#039; =&amp;gt; 2.1&lt;br /&gt;
       )&lt;br /&gt;
   )&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In certain situations more advanced configuration is required - for example specification of a proxy server to use. This configuration must be specified in $CFG-&amp;gt;behat_config.&lt;br /&gt;
Note: If a value is specified in both behat_config, and behat_profiles, the version in behat_profiles will take precedence&lt;br /&gt;
&lt;br /&gt;
==== behat_config ====&lt;br /&gt;
Structure of $CFG-&amp;gt;behat_config has been modified and you should now use&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_config = array(&lt;br /&gt;
   &#039;phantomjs&#039; =&amp;gt; array(&lt;br /&gt;
      &#039;suites&#039; =&amp;gt; array (&lt;br /&gt;
          &#039;default&#039; =&amp;gt; array(&lt;br /&gt;
              &#039;filters&#039; =&amp;gt; array(&lt;br /&gt;
                 &#039;tags&#039; =&amp;gt; &#039;~@_file_upload&amp;amp;&amp;amp;~@_alert&amp;amp;&amp;amp;~@_bug_phantomjs&#039;&lt;br /&gt;
              ),&lt;br /&gt;
           ),&lt;br /&gt;
       ),&lt;br /&gt;
       &#039;extensions&#039; =&amp;gt; array(&lt;br /&gt;
            &#039;Behat\MinkExtension&#039; =&amp;gt; array(&lt;br /&gt;
                  &#039;selenium2&#039; =&amp;gt; array(&lt;br /&gt;
                      &#039;browser&#039; =&amp;gt; &#039;phantomjs&#039;,&lt;br /&gt;
                      &#039;wd_host&#039; =&amp;gt; &amp;quot;http://127.0.0.1:4443/wd/hub&amp;quot;&lt;br /&gt;
                  )&lt;br /&gt;
              )&lt;br /&gt;
          )&lt;br /&gt;
     ),&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
=== Formats and outputs ===&lt;br /&gt;
html and failed outputs are not maintained by behat. We are planning to include [https://github.com/dutchiexl/BehatHtmlFormatterPlugin BehatHTMLFormatter plugin] maintained by Neal Vanmeert and write our custom failed formatter. Changes required to use formatter and outputs are&lt;br /&gt;
====Single option per formatter====&lt;br /&gt;
Multiple formats should be sent as separate options with respective output option&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
     vendor/bin/behat --config /PATH/FOR/BEHAT_DATAROOT/behat/behat.yml --format=moodle_progress --out=/PATH/TO/SAVE/progress.txt --format=moodle_screenshot --out=/PATH/TO/SAVE/SCREENSHOT_DIR&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====Terminal output is std====&lt;br /&gt;
For output to terminal use std&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
      vendor/bin/behat --config /PATH/FOR/BEHAT_DATAROOT/behat/behat.yml --format=moodle_progress --out=std&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====Formatter can be used once====&lt;br /&gt;
One formatter is supported for each run. Means if you want to output progress to file and terminal then it is not supported. You need to use 2 different formatter like moodle_progress and progress&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
      vendor/bin/behat --config /PATH/FOR/BEHAT_DATAROOT/behat/behat.yml --format=moodle_progress --out=std --format=progress --out=/PATH/TO/SAVE/progress.txt&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====Custom formatters====&lt;br /&gt;
Following custom formatter are supported&lt;br /&gt;
# &#039;&#039;&#039;moodle_progress&#039;&#039;&#039;: Prints Moodle branch information and dots for each step.&lt;br /&gt;
# &#039;&#039;&#039;moodle_list&#039;&#039;&#039;: List all scenarios.&lt;br /&gt;
# &#039;&#039;&#039;moodle_stepcount&#039;&#039;&#039;: List all features with total steps in each feature file. Used for parallel run.&lt;br /&gt;
# &#039;&#039;&#039;moodle_screenshot&#039;&#039;&#039;: Take screenshot and core dump of each step. With following options you can dump either or both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;image&amp;quot;}&#039;**: will dump image only&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;}&#039;**: will dump html only.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html,image&amp;quot;}&#039;**: will dump both.&lt;br /&gt;
## --format-settings &#039;{&amp;quot;formats&amp;quot;: &amp;quot;html&amp;quot;, &amp;quot;dir_permissions&amp;quot;: &amp;quot;0777&amp;quot;}&#039;**&lt;br /&gt;
&lt;br /&gt;
=== Running test ===&lt;br /&gt;
====Rerun option doesn&#039;t accept filepath====&lt;br /&gt;
Rerun should be used without the filepath, as the failed scenarios are now cached by behat and rerun.&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
vendor/bin/behat --config /PATH/FOR/BEHAT_DATAROOT/behat/behat.yml&lt;br /&gt;
exitcode=${PIPESTATUS[0]}&lt;br /&gt;
if [ &amp;quot;${exitcode}&amp;quot; -ne 0 ]; then&lt;br /&gt;
  vendor/bin/behat --config /PATH/FOR/BEHAT_DATAROOT/behat/behat.yml --rerun&lt;br /&gt;
fi&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
====Rerun for parallel run====&lt;br /&gt;
Rerun file is cached with a unique key which is generated using input arguments. As in parallel run they all are same, the rerun file gets overwritten by the last failing run. To avoid this:&lt;br /&gt;
# Add 3 profiles in your config for each run say: firefox_1, firefox_2, firefox_3 for 3 parallel runs&lt;br /&gt;
# use --profile=&amp;quot;firefox_{runprocess} --replace=&amp;quot;{runprocess}&amp;quot; options for run.php&lt;br /&gt;
::: NOTE: [https://github.com/Behat/Behat/issues/859 Behat issue] have been waiting for this to be resolved by including basepath to generate the unique key.&lt;br /&gt;
&lt;br /&gt;
====Exit code for parallel run is bitmasked====&lt;br /&gt;
Exit code is now &#039;&#039;&#039;bitmasked&#039;&#039;&#039; contains pass/fail information of each run in every bit (starting from 0th bit). Example: If you have 3 parallel runs, out of which Run 1 and 3 failed. Then exit code will be 5 (0b0101). This will help to identify which runs need to be executed again for avoiding random failures.&lt;br /&gt;
:: If you want to use specific path where failed scenarios should be saved for rerun then you can set the following config:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;behat_config = array(&lt;br /&gt;
    &#039;default&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;testers&#039; =&amp;gt; array(&lt;br /&gt;
            &#039;rerun_cache&#039; =&amp;gt; &#039;/home/rajesh/Desktop/rerun&#039;&lt;br /&gt;
        ),&lt;br /&gt;
    ),&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Failed output ===&lt;br /&gt;
&#039;&#039;&#039;Failed output is different&#039;&#039;&#039;: Failed output display step and line in file where it failed and not the scenario line number.&lt;br /&gt;
&lt;br /&gt;
=== Changes required in context file ===&lt;br /&gt;
====Custom chained steps support====&lt;br /&gt;
Chained steps are not supported by Behat 3. We have added support for custom chained step via moodle-behat-extension, but planning to depreciate this in future. To make your context backward compatible you should replace chained steps with the api calls in your context. Example the following chained steps&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$steps = array(&lt;br /&gt;
            new Given(&#039;I click on &amp;quot;&#039; . get_string(&#039;login&#039;) . &#039;&amp;quot; &amp;quot;link&amp;quot; in the &amp;quot;.logininfo&amp;quot; &amp;quot;css_element&amp;quot;&#039;),		&lt;br /&gt;
            new Given(&#039;I set the field &amp;quot;&#039; . get_string(&#039;username&#039;) . &#039;&amp;quot; to &amp;quot;&#039; . $this-&amp;gt;escape($username) . &#039;&amp;quot;&#039;),		&lt;br /&gt;
            new Given(&#039;I set the field &amp;quot;&#039; . get_string(&#039;password&#039;) . &#039;&amp;quot; to &amp;quot;&#039;. $this-&amp;gt;escape($username) . &#039;&amp;quot;&#039;),		&lt;br /&gt;
            new Given(&#039;I press &amp;quot;&#039; . get_string(&#039;login&#039;) . &#039;&amp;quot;&#039;)		&lt;br /&gt;
        );&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
can be replaced with&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Click on login link.&lt;br /&gt;
$this-&amp;gt;execute(&#039;behat_general::i_click_on_in_the&#039;,&lt;br /&gt;
    array(get_string(&#039;login&#039;), &#039;link&#039;, &#039;.logininfo&#039;, &#039;css_element&#039;));&lt;br /&gt;
// Enter username and password.&lt;br /&gt;
$this-&amp;gt;execute(&#039;behat_forms::i_set_the_field_to&#039;, array(&#039;Username&#039;, $this-&amp;gt;escape($username)));&lt;br /&gt;
$this-&amp;gt;execute(&#039;behat_forms::i_set_the_field_to&#039;, array(&#039;Password&#039;, $this-&amp;gt;escape($username)));&lt;br /&gt;
// Press log in button.&lt;br /&gt;
$this-&amp;gt;execute(&#039;behat_forms::press_button&#039;, get_string(&#039;login&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====TableNode==== &lt;br /&gt;
Table Node class has changed and requires following modifications:&lt;br /&gt;
# TableNode constructor has changed, and it requires least empty array(). &lt;br /&gt;
# addRow() api is deprecated, if you are modifying rows then save that in an array and create a new TableNode()&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$rows = $options-&amp;gt;getRows();&lt;br /&gt;
$newrows = array();&lt;br /&gt;
foreach ($rows as $k =&amp;gt; $data) {&lt;br /&gt;
    $newrows[$k] = strtr($data, &amp;quot;äåö&amp;quot;, &amp;quot;aao&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
$modifiedtable = = new TableNode($newrows);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Named selectors are depreciated====&lt;br /&gt;
Named selector is deprecated, you should either use &#039;&#039;&#039;named_exact&#039;&#039;&#039; or &#039;&#039;&#039;named_partial&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;find(&#039;named_partial&#039;, array(&#039;link&#039;, get_string(&#039;hide&#039;)), $nohideexception, $activitynode);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=49563</id>
		<title>Javascript Modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=49563"/>
		<updated>2016-03-02T10:12:20Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Fixed typos&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
= Javascript Modules =&lt;br /&gt;
&lt;br /&gt;
== What is a Javascript module and why do I care? ==&lt;br /&gt;
&lt;br /&gt;
A Javascript module is nothing more than a collection of Javascript code that can be used (reliably) from other pieces of Javascript. &lt;br /&gt;
&lt;br /&gt;
== Why should I package my code as a module? ==&lt;br /&gt;
&lt;br /&gt;
By packaging your code as a module you break your code up into smaller reusable pieces. This is good because:&lt;br /&gt;
&lt;br /&gt;
a) Each smaller piece is simpler to understand / debug&lt;br /&gt;
&lt;br /&gt;
b) Each smaller piece is simpler to test&lt;br /&gt;
&lt;br /&gt;
c) You can re-use common code instead of duplicating it&lt;br /&gt;
&lt;br /&gt;
= How do I write a Javascript module in Moodle? =&lt;br /&gt;
&lt;br /&gt;
Since version 2.9, Moodle supports Javascript modules written using the Asynchronous Module Definition ([https://github.com/amdjs/amdjs-api/wiki/AMD AMD]) API. This is a standard API for creating Javascript modules and you will find many useful third party libraries that are already using this format. &lt;br /&gt;
&lt;br /&gt;
To edit or create an AMD module in Moodle you need to do a couple of things. &lt;br /&gt;
&lt;br /&gt;
== Install grunt ==&lt;br /&gt;
&lt;br /&gt;
The AMD modules in Moodle must be processed by some build tools before they will be visible to your web browser. We use &amp;quot;[http://gruntjs.com/ grunt]&amp;quot; as a build tool to wrap our different processes. Grunt is a build tool written in Javascript that runs in the &amp;quot;[http://nodejs.org/ nodejs]&amp;quot; environment.&lt;br /&gt;
&lt;br /&gt;
This means you first have to &#039;&#039;&#039;install nodejs&#039;&#039;&#039; - and its package manager [https://www.npmjs.com/ npm]. The details of how to install those packages will vary by operating system, but on Linux it&#039;s probably similar to &amp;quot;sudo apt-get install nodejs npm&amp;quot;. There are downloadable packages for other operating systems here: http://nodejs.org/download/.&lt;br /&gt;
&lt;br /&gt;
Once this is done, you can &#039;&#039;&#039;run the command&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
 npm install&lt;br /&gt;
 npm install -g grunt-cli&lt;br /&gt;
&lt;br /&gt;
from the top of the Moodle directory to install all of the required tools. (You may need extra permissions to use the -g option.)&lt;br /&gt;
&lt;br /&gt;
== Running grunt ==&lt;br /&gt;
&lt;br /&gt;
See [[Grunt#Running_grunt]] for more details of specific grunt commands which can be used.&lt;br /&gt;
&lt;br /&gt;
== &amp;quot;Hello World&amp;quot; I am a Javascript Module ==&lt;br /&gt;
Lets now create a simple Javascript module so we can see how to lay things out. &lt;br /&gt;
&lt;br /&gt;
Each Javascript module is contained in a single source file in the &amp;lt;componentdir&amp;gt;/amd/src folder. The final name of the module is taken from the file name and the component name. E.g. block_overview/amd/src/helloworld.js would be a module named &amp;quot;block_overview/helloworld&amp;quot;. the name of the module is important when you want to call it from somewhere else in the code. &lt;br /&gt;
&lt;br /&gt;
After running grunt - the minified Javascript files are stored in the &amp;lt;componentdir&amp;gt;/amd/build folder. The javascript files are renamed to show that they are minified (helloworld.js becomes helloworld.min.js). &lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to add the built files (the ones in amd/build) to your git commits, or in production no-one will see your changes. &lt;br /&gt;
&lt;br /&gt;
Lets create a simple module now:&lt;br /&gt;
&lt;br /&gt;
blocks/overview/amd/src/helloworld.js&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Standard license block omitted.&lt;br /&gt;
/*&lt;br /&gt;
 * @package    block_overview&lt;br /&gt;
 * @copyright  2015 Someone cool&lt;br /&gt;
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
 /**&lt;br /&gt;
  * @module block_overview/helloworld&lt;br /&gt;
  */&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
     /** &lt;br /&gt;
      * Give me blue.&lt;br /&gt;
      * @access private&lt;br /&gt;
      * @return {string}&lt;br /&gt;
      */&lt;br /&gt;
     var makeItBlue = function() {&lt;br /&gt;
          // We can use our jquery dependency here.&lt;br /&gt;
          return $(&#039;.blue&#039;).show();&lt;br /&gt;
     };&lt;br /&gt;
      &lt;br /&gt;
    /**&lt;br /&gt;
     * @constructor&lt;br /&gt;
     * @alias module:block_overview/helloworld&lt;br /&gt;
     */&lt;br /&gt;
    var greeting = function() {&lt;br /&gt;
        /** @access private */&lt;br /&gt;
        var privateThoughts = &#039;I like the colour blue&#039;;&lt;br /&gt;
        &lt;br /&gt;
        /** @access public */&lt;br /&gt;
        this.publicThoughts = &#039;I like the colour orange&#039;;&lt;br /&gt;
&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * A formal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.formal = function() {&lt;br /&gt;
        return &#039;How do you do?&#039;;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * An informal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.informal = function() {&lt;br /&gt;
        return &#039;Wassup!&#039;;&lt;br /&gt;
    };&lt;br /&gt;
    return greeting;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most interesting line above is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All AMD modules must call &amp;quot;define()&amp;quot; as the first and only global scoped piece of code. This ensures the javascript code contains no global variables and will not conflict with any other loaded module. The name of the module does not need to be specified because it is determined from the filename and component (but it can be listed in a comment for JSDoc as shown here). &lt;br /&gt;
&lt;br /&gt;
The first argument to &amp;quot;define&amp;quot; is the list of dependencies for the module. This argument must be passed as an array, even if there is only one. In this example &amp;quot;jquery&amp;quot; is a dependency. &amp;quot;jquery&amp;quot; is shipped as a core module is available to all AMD modules. &lt;br /&gt;
&lt;br /&gt;
The second argument to &amp;quot;define&amp;quot; is the function that defines the module. This function will receive as arguments, each of the requested dependencies in the same order they were requested. In this example we receive JQuery as an argument and we name the variable &amp;quot;$&amp;quot; (it&#039;s a JQuery thing). We can then access JQuery normally through the $ variable which is in scope for any code in our module. &lt;br /&gt;
&lt;br /&gt;
The rest of the code in this example is a standard way to define a Javascript module with public/private variables and methods. There are many ways to do this, this is only one.&lt;br /&gt;
&lt;br /&gt;
It is important that we are returning &#039;greeting&#039;. If there is no return then your module will be declared as undefined.&lt;br /&gt;
&lt;br /&gt;
== Loading modules dynamically ==&lt;br /&gt;
What do you do if you don&#039;t know in advance which modules will be required? Stuffing all possible required modules in the define call is one solution, but it&#039;s ugly and it only works for code that is in an AMD module (what about inline code in the page?). AMD lets you load a dependency any time you like. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Load a new dependency.&lt;br /&gt;
require([&#039;mod_wiki/timer&#039;], function(timer) {&lt;br /&gt;
   // timer is available to do my bidding.&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Including an external javascript/jquery library ==&lt;br /&gt;
If you want to include a javascript / jquery library downloaded from the internet you can do so as follows:&lt;br /&gt;
&lt;br /&gt;
If the library is in AMD format and has a define:&lt;br /&gt;
e.g. i want to include the jquery final countdown timer on my page ( hilios.github.io/jQuery.countdown/ )&lt;br /&gt;
* download the module in both normal and minified versions&lt;br /&gt;
* place the modules in your moodle install e.g. your custom theme dir, or plugin dir&lt;br /&gt;
* /theme/mytheme/amd/src/jquery.countdown.js&lt;br /&gt;
&lt;br /&gt;
you can now include the module and initialise it (there are multiple ways to do this)&lt;br /&gt;
php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// 1. Create your own amd module and initialise it:&lt;br /&gt;
$this-&amp;gt;page-&amp;gt;requires-&amp;gt;js_call_amd(&#039;theme_mytheme/countdowntimer&#039;, &#039;initialise&#039;, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
javascript:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
//1. put this code in theme/mytheme/amd/src/countdowntimer.js&lt;br /&gt;
define([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($) {&lt;br /&gt;
    return {&lt;br /&gt;
        initialise: function ($params) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. put the javascript into a mustache template&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/templates/countdowntimer.mustache&lt;br /&gt;
&amp;lt;span id=&amp;quot;clock&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
require([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. call the javascript directly from php -- although who would want to put javascript into php? ergh..&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_amd_inline(&#039;&lt;br /&gt;
require([&#039;theme_mytheme/jquery.countdown&#039;], function(min) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Embedding AMD code in a page ==&lt;br /&gt;
So you have created lots of cool Javascript modules. Great. How do we actually call them? Any javascript code that calls an AMD module must execute AFTER the requirejs module loader has finished loading. We have provided a function &amp;quot;js_call_amd&amp;quot; that will call a single function from an AMD module with parameters.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_call_amd($modulename, $functionname, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
that will &amp;quot;do the right thing&amp;quot; with your block of AMD code and execute it at the end of the page, after our AMD module loader has loaded. &lt;br /&gt;
Notes:&lt;br /&gt;
* the $modulename is the &#039;componentname/modulename&#039; discussed above&lt;br /&gt;
* the $functionname is the name of a public function exposed by the amd module. &lt;br /&gt;
* the $params is an array of params passed as arguments to the function. These should be simple types that can be handled by json_encode (no recursive arrays, or complex classes please). &lt;br /&gt;
* if the size of the params array is too large (&amp;gt; 1Kb), this will produce a developer warning. Do not attempt to pass large amounts of data through this function, it will pollute the page size. A preferred approach is to pass css selectors for DOM elements that contain data-attributes for any required data, or fetch data via ajax in the background.&lt;br /&gt;
&lt;br /&gt;
AMD / JS code can also be embedded on a page via mustache templates&lt;br /&gt;
see here: https://docs.moodle.org/dev/Templates#What_if_a_template_contains_javascript.3F&lt;br /&gt;
&lt;br /&gt;
== But I have a mega JS file I don&#039;t want loaded on every page? ==&lt;br /&gt;
Loading all JS files at once and stuffing them in the browser cache is the right choice for MOST js files, there are probably some exceptions. For these files, you can rename the javascript file to end with the suffix &amp;quot;-lazy.js&amp;quot; which indicates that the module will not be loaded by default, it will be requested the first time it is used. There is no difference in usage for lazy loaded modules, the require() call looks exactly the same, it&#039;s just that the module name will also have the &amp;quot;-lazy&amp;quot; suffix.&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Writing_PHPUnit_tests&amp;diff=48644</id>
		<title>Writing PHPUnit tests</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Writing_PHPUnit_tests&amp;diff=48644"/>
		<updated>2015-09-30T16:00:56Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.3}}&lt;br /&gt;
&lt;br /&gt;
Moodle PHPUnit integration is designed to allow easy adding of new tests. At the start of each test the state is automatically reset to fresh new installation (unless explicitly told not to reset).&lt;br /&gt;
&lt;br /&gt;
=Testcase classes=&lt;br /&gt;
&lt;br /&gt;
There are two basic test class that are supposed to used in all Moodle unit tests - basic_testcase and advanced_testcase. &#039;&#039;&#039;Please note it is strongly recommended to put only one testcase into each class file.&#039;&#039;&#039;&lt;br /&gt;
;basic_testcase : Very simple tests that do not modify database, dataroot or any PHP globals. It can be used for example when trying examples from the official PHPUnit tutorial.&lt;br /&gt;
;advanced_testcase : Enhanced testcase class enhanced for easy testing of Moodle code.&lt;br /&gt;
&lt;br /&gt;
There is a third testcase class that is specially designed for testing of our Moodle database layer, it should not be used for other purposes.&lt;br /&gt;
&lt;br /&gt;
== Assertions ==&lt;br /&gt;
&lt;br /&gt;
The complete list of assertions can be found in the [http://www.phpunit.de/manual/3.7/en/appendixes.assertions.html phpunit manual].&lt;br /&gt;
==Sample plugin testcase==&lt;br /&gt;
&lt;br /&gt;
PHPUnit tests are located in &amp;lt;code&amp;gt;tests/*_test.php&amp;lt;/code&amp;gt; files in your plugin, for example mod/myplugin/tests/sample_test.php, the file should contain only one class that extends &amp;lt;code&amp;gt;advanced_testcase&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 class mod_myplugin_sample_testcase extends advanced_testcase {&lt;br /&gt;
     public function test_adding() {&lt;br /&gt;
         $this-&amp;gt;assertEquals(2, 1+2);&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
See [[PHPUnit integration#Class and file naming rules]] for more information.&lt;br /&gt;
&lt;br /&gt;
==Inclusion of Moodle library files==&lt;br /&gt;
&lt;br /&gt;
If you want to include some Moodle library files you should always declare &#039;&#039;&#039;global $CFG&#039;&#039;&#039;. The reason is that testcase files may be included from non-moodle code which does not make the global $CFG available automatically.&lt;br /&gt;
&lt;br /&gt;
==Automatic state reset==&lt;br /&gt;
By default after each test Moodle database and dataroot is automatically reset to the original state which was present right after installation. make sure to use $this-&amp;gt;resetAfterTest() to indicate that the database or changes of standard global variables are expected.&lt;br /&gt;
&lt;br /&gt;
If you received the error &amp;quot;Warning: unexpected database modification, resetting DB state&amp;quot; it is because the test is not using $this-&amp;gt;resetAfterTest().&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 class mod_myplugin_testcase extends advanced_testcase {&lt;br /&gt;
     public function test_deleting() {&lt;br /&gt;
         global $DB;&lt;br /&gt;
         $this-&amp;gt;resetAfterTest(true);&lt;br /&gt;
         $DB-&amp;gt;delete_records(&#039;user&#039;);&lt;br /&gt;
         $this-&amp;gt;assertEmpty($DB-&amp;gt;get_records(&#039;user&#039;));&lt;br /&gt;
     }&lt;br /&gt;
     public function test_user_table_was_reset() {&lt;br /&gt;
         global $DB;&lt;br /&gt;
         $this-&amp;gt;assertEquals(2, $DB-&amp;gt;count_records(&#039;user&#039;, array()));&lt;br /&gt;
     }&lt;br /&gt;
 }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Generators=&lt;br /&gt;
&lt;br /&gt;
Tests that need to modify default installation may use generators to create new courses, users, etc. All examples on this page should be used from test methods of a test class derived from advanced_testcase.&lt;br /&gt;
&lt;br /&gt;
Note if you are using PHPUnit [https://phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers @dataProvider] functions to provide parameters to unit tests, you can not use the data generator or change the user etc in the data provider function.&lt;br /&gt;
&lt;br /&gt;
==Creating users==&lt;br /&gt;
At the start of each test there are only two users present - guest and administrator. If you need to add more test accounts use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $user = $this-&amp;gt;getDataGenerator()-&amp;gt;create_user();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You may also specify properties of the user account, for example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $user1 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_user(array(&#039;email&#039;=&amp;gt;&#039;user1@example.com&#039;, &#039;username&#039;=&amp;gt;&#039;user1&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
By default no user is logged-in, use setUser() method to change current $USER value:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $this-&amp;gt;setUser($user1);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Guest and admin accounts have a shortcut methods:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $this-&amp;gt;setGuestUser();&lt;br /&gt;
 $this-&amp;gt;setAdminUser();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Null can be used to set current user back to not-logged-in:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $this-&amp;gt;setUser(null);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating course categories==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $category1 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_category();&lt;br /&gt;
 $category2 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_category(array(&#039;name&#039;=&amp;gt;&#039;Some subcategory&#039;, &#039;parent&#039;=&amp;gt;$category1-&amp;gt;id));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating courses==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $course1 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_course();&lt;br /&gt;
 &lt;br /&gt;
 $category = $this-&amp;gt;getDataGenerator()-&amp;gt;create_category();&lt;br /&gt;
 $course2 = $this-&amp;gt;getDataGenerator()-&amp;gt;create_course(array(&#039;name&#039;=&amp;gt;&#039;Some course&#039;, &#039;category&#039;=&amp;gt;$category-&amp;gt;id));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating activities==&lt;br /&gt;
&lt;br /&gt;
Some activity plugins include instance generators. The generator class are defined in plugindirectory/tests/generator/lib.php.&lt;br /&gt;
&lt;br /&gt;
Example of creation of new course with one page resource:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $course = $this-&amp;gt;getDataGenerator()-&amp;gt;create_course();&lt;br /&gt;
 $generator = $this-&amp;gt;getDataGenerator()-&amp;gt;get_plugin_generator(&#039;mod_page&#039;);&lt;br /&gt;
 $generator-&amp;gt;create_instance(array(&#039;course&#039;=&amp;gt;$course-&amp;gt;id));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating cohorts==&lt;br /&gt;
{{Moodle 2.4}}&lt;br /&gt;
Since 2.4 there the data generator supports creation of new cohorts.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $cohort = $this-&amp;gt;getDataGenerator()-&amp;gt;create_cohort();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Simplified user enrolments==&lt;br /&gt;
{{Moodle 2.4}}&lt;br /&gt;
Instead of standard enrolment API it is possible to use simplified method in data generator. It is intended to be used with self and manual enrolment plugins.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;enrol_user($userid, $courseid);&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;enrol_user($userid, $courseid, $teacherroleid);&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;enrol_user($userid, $courseid, $teacherroleid, &#039;manual&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating scales==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_scale();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_scale(array(&#039;name&#039; =&amp;gt; $name, &#039;scale&#039; =&amp;gt; $scale, &#039;courseid&#039; =&amp;gt; $courseid, &#039;userid&#039; =&amp;gt; $userid, &#039;description&#039; =&amp;gt; description, &#039;descriptionformat&#039; =&amp;gt; $descriptionformat));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating roles==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_role();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_role(array(&#039;shortname&#039; =&amp;gt; $shortname, &#039;name&#039; =&amp;gt; $name, &#039;description&#039; =&amp;gt; description, &#039;archetype&#039; =&amp;gt; $archetype));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating tags==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_tag();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_tag(array(&lt;br /&gt;
    &#039;userid&#039; =&amp;gt; $userid, &lt;br /&gt;
    &#039;rawname&#039; =&amp;gt; $rawname,&lt;br /&gt;
    &#039;name&#039; =&amp;gt; $name, &lt;br /&gt;
    &#039;tagtype&#039; =&amp;gt; $tagtype,&lt;br /&gt;
    &#039;description&#039; =&amp;gt; $description, &lt;br /&gt;
    &#039;descriptionformat&#039; =&amp;gt; $descriptionformat,&lt;br /&gt;
    &#039;flag&#039; =&amp;gt; $flag&lt;br /&gt;
));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Groups==&lt;br /&gt;
&lt;br /&gt;
===Creating groups===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_group(array(&#039;courseid&#039; =&amp;gt; $courseid));&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_group(array(&#039;courseid&#039; =&amp;gt; $courseid, &#039;name&#039; =&amp;gt; $name, &#039;description&#039; =&amp;gt; $description, &#039;descriptionformat&#039; =&amp;gt; $descriptionformat));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Adding users to groups===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_group_member(array(&#039;userid&#039; =&amp;gt; $userid, &#039;groupid&#039; =&amp;gt; $groupid));&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_group_member(array(&#039;userid&#039; =&amp;gt; $userid, &#039;groupid&#039; =&amp;gt; $groupid, &#039;component&#039; =&amp;gt; $component, &#039;itemid&#039; =&amp;gt; $itemid));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Creating groupings===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grouping(array(&#039;courseid&#039; =&amp;gt; $courseid));&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grouping(array(&#039;courseid&#039; =&amp;gt; $courseid, &#039;name&#039; =&amp;gt; $name, &#039;description&#039; =&amp;gt; $description, &#039;descriptionformat&#039; =&amp;gt; $descriptionformat));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Adding groups to groupings===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grouping_group(array(&#039;groupingid&#039; =&amp;gt; $groupingid, &#039;groupid&#039; =&amp;gt; $groupid));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Repositories==&lt;br /&gt;
&lt;br /&gt;
===Creating repository instances===&lt;br /&gt;
{{Moodle 2.5}}&lt;br /&gt;
Some respository plugins include instance generators. The generator class are defined in plugindirectory/tests/generator/lib.php..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_repository($type, $record, $options);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Creating repository types===&lt;br /&gt;
{{Moodle 2.5}}&lt;br /&gt;
Some respository plugins include type generators. The generator class are defined in plugindirectory/tests/generator/lib.php..&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_repository_type($type, $record, $options);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Creating grades==&lt;br /&gt;
&lt;br /&gt;
===Grade categories===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_category(array(&#039;courseid&#039; =&amp;gt; $courseid));&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_category(array(&#039;courseid&#039; =&amp;gt; $courseid, &#039;fullname&#039; =&amp;gt; $fullname));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Grade items===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_item();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_item(array(&#039;itemtype&#039; =&amp;gt; $itemtype, &#039;itemname&#039; =&amp;gt; $itemname, &#039;outcomeid&#039; =&amp;gt; $outcomeid, &#039;scaleid&#039; =&amp;gt; $scaleid, &#039;gradetype&#039; =&amp;gt; $gradetype));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Outcomes===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_outcome();&lt;br /&gt;
$this-&amp;gt;getDataGenerator()-&amp;gt;create_grade_item(array(&#039;fullname&#039; =&amp;gt; $fullname));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Other types of plugin==&lt;br /&gt;
{{Moodle 2.5}}&lt;br /&gt;
Any other type of plugin can have a generator. The generator class should extend component_generator_base, and then you can get an instance using $mygenerator = $this-&amp;gt;getDataGenerator()-&amp;gt;get_plugin_generator($frankenstylecomponentname);&lt;br /&gt;
&lt;br /&gt;
For some types of plugin, like mod documented above, there may be a more specific class than component_generator_base to extend, like testing_module_generator. That will give a consistent set of method names to use. Otherwise, you can create whatever methods you like on your generator, to create the different things you need to work whith.&lt;br /&gt;
&lt;br /&gt;
=Long tests=&lt;br /&gt;
&lt;br /&gt;
All standard test should execute as fast as possible. Tests that take a loner time to execute (&amp;gt;10s) or are otherwise expensive (such as querying external servers that might be flooded by all dev machines) should be execute only when PHPUNIT_LONGTEST is true. This constant can be set in phpunit.xml or directly in config.php.&lt;br /&gt;
&lt;br /&gt;
=Large test data=&lt;br /&gt;
See advanced_testcase::createXMLDataSet() and advanced_testcase::createCsvDataSet() and related functions there for easier ways to manage large test data sets within files rather than arrays in code. See [[PHPUnit_integration#Extra_methods]]&lt;br /&gt;
&lt;br /&gt;
=Testing sending of messages=&lt;br /&gt;
{{Moodle 2.4}}&lt;br /&gt;
You can temporarily redirect all messages sent via message_send() to a message sink object. This allows developers to verify that the tested code is sending expected messages.&lt;br /&gt;
&lt;br /&gt;
To test code using messaging first disable the use of transactions and then redirect the messaging into a new message sink, you can inspect the results later.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;preventResetByRollback();&lt;br /&gt;
$sink = $this-&amp;gt;redirectMessages();&lt;br /&gt;
//... code that is sending messages&lt;br /&gt;
$messages = $sink-&amp;gt;get_messages();&lt;br /&gt;
$this-&amp;gt;assertEquals(3, count($messages));&lt;br /&gt;
//.. test messages were generated in correct order with appropriate content&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Testing sending of emails=&lt;br /&gt;
{{Moodle 2.6}}&lt;br /&gt;
You can temporarily redirect emails sent via email_to_user() to a email message sink object. This allows developers to verify that the tested code is sending expected emails.&lt;br /&gt;
&lt;br /&gt;
To test code using messaging first unset &#039;noemailever&#039; setting and then redirect the emails into a new message sink where you can inspect the results later.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
unset_config(&#039;noemailever&#039;);&lt;br /&gt;
$sink = $this-&amp;gt;redirectEmails();&lt;br /&gt;
//... code that is sending email&lt;br /&gt;
$messages = $sink-&amp;gt;get_messages();&lt;br /&gt;
$this-&amp;gt;assertEquals(1, count($messages));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Extra test settings=&lt;br /&gt;
&lt;br /&gt;
Usually the test should not interact with any external systems and it should work the same on all systems. But sometimes you need to specify some option for connection to external systems or system configuration. It is intentionally not possible to use $CFG settings from config.php.&lt;br /&gt;
&lt;br /&gt;
There are several ways how to inject your custom settings:&lt;br /&gt;
* define test setting constants in your phpunit.xml file&lt;br /&gt;
* define test setting constants in your config.php&lt;br /&gt;
&lt;br /&gt;
These constants may be then used in your test or plugin code.&lt;br /&gt;
&lt;br /&gt;
=See also=&lt;br /&gt;
* [[PHPUnit integration]]&lt;br /&gt;
* [[PHPUnit]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Unit testing]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User:Mark_Johnson/Output_Components_Cheatsheet&amp;diff=47564</id>
		<title>User:Mark Johnson/Output Components Cheatsheet</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User:Mark_Johnson/Output_Components_Cheatsheet&amp;diff=47564"/>
		<updated>2015-04-10T09:39:27Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I produced this as an at-a-glance reference for use when writing a plugin renderer.&lt;br /&gt;
In yellow, it shows all the methods of &amp;lt;tt&amp;gt;html_writer&amp;lt;/tt&amp;gt;, and all of the methods provided by the &amp;lt;tt&amp;gt;$OUTPUT&amp;lt;/tt&amp;gt; global (or &amp;lt;tt&amp;gt;$this-&amp;gt;output&amp;lt;/tt&amp;gt; in a plugin renderer). In blue, it shows the APIs for displaying tables. The sheet fits on 2 sides of A4, is displayed below as PNGs or is downloadable as [https://cloud.barrenfrozenwasteland.com/owncloud/index.php/s/JFuMvaGnlzhaqZH PDF]&lt;br /&gt;
&lt;br /&gt;
[[Image:output_cheatsheet_side1.png|Output Cheatsheet Side 1]]&lt;br /&gt;
[[Image:output_cheatsheet_side2.png|Output Cheatsheet Side 2]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Unity_Webapp&amp;diff=34614</id>
		<title>Unity Webapp</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Unity_Webapp&amp;diff=34614"/>
		<updated>2012-08-01T08:22:19Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* All Users */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Ubuntu&#039;s Unity interface has a new &amp;quot;[http://developer.ubuntu.com/api/ubuntu-12.04/javascript/index.html Web API]&amp;quot; feature, allowing web applications to integrate with the desktop.  It would be nice to add some integration to Moodle for Ubuntu users.  This page is to brainstorm possible integrations for various types of user.&lt;br /&gt;
&lt;br /&gt;
== Launcher ==&lt;br /&gt;
The launcher is where the icons for launching applications live.  The Web API allows a web application to have its own icon in the launcher. It also allows a &amp;quot;Quick List&amp;quot; to be displayed on right click allowing quick access to application features (e.g. opening a new document, play/pausing music).&lt;br /&gt;
&lt;br /&gt;
== HUD ==&lt;br /&gt;
The HUD aggregates application menus in a searchable interface which can be summoned by pressing ALT.  The Web API allows a web application to add its own &amp;quot;menu&amp;quot; entries to the HUD, which can then be searched alongside other application menus.&lt;br /&gt;
&lt;br /&gt;
=== Administrators ===&lt;br /&gt;
The HUD could contain the Site Administation hierachy from the Settings block.&lt;br /&gt;
&lt;br /&gt;
=== Teachers ===&lt;br /&gt;
When on a course page, the HUD could contain the Course Administration links from the Settings block&lt;br /&gt;
&lt;br /&gt;
== Messaging Menu ==&lt;br /&gt;
The Messaging Menu sits in the top-right of the screen, by the clock.  It displays services such as Email, Microblogging etc that the user is connected to, links to write new messages etc, and an indication of new or unread messages for each service.  Webapps allow a web application to add it&#039;s own messaging service to this indicator.&lt;br /&gt;
&lt;br /&gt;
=== All Users ===&lt;br /&gt;
When the Messages block is visible on a page, the Messaging menu could contain an indication of unread messages, with links to view messages and write a new message&lt;br /&gt;
&lt;br /&gt;
== Notifications ==&lt;br /&gt;
Notifications are unobtrusive bubbles that display event notifications from applications (&amp;quot;There are new updates available&amp;quot;, &amp;quot;Your backup has started&amp;quot;, etc.). The Web API allows web application to display their own event notifications.&lt;br /&gt;
&lt;br /&gt;
=== All Users ===&lt;br /&gt;
Create a new [[Messaging consumer]] to enable desktop notifications as an option for receiving Moodle notifications when logged in.&lt;br /&gt;
&lt;br /&gt;
== Media Menu ==&lt;br /&gt;
The Media Menu contains the global volume control, but can also display details of currently playing media from running media applications, with playback controls.  The Web API allows a web application add these controls.&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Unity_Webapp&amp;diff=34613</id>
		<title>Unity Webapp</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Unity_Webapp&amp;diff=34613"/>
		<updated>2012-08-01T08:20:04Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Ubuntu&#039;s Unity interface has a new &amp;quot;[http://developer.ubuntu.com/api/ubuntu-12.04/javascript/index.html Web API]&amp;quot; feature, allowing web applications to integrate with the desktop.  It would be nice to add some integration to Moodle for Ubuntu users.  This page is to brainstorm possible integrations for various types of user.&lt;br /&gt;
&lt;br /&gt;
== Launcher ==&lt;br /&gt;
The launcher is where the icons for launching applications live.  The Web API allows a web application to have its own icon in the launcher. It also allows a &amp;quot;Quick List&amp;quot; to be displayed on right click allowing quick access to application features (e.g. opening a new document, play/pausing music).&lt;br /&gt;
&lt;br /&gt;
== HUD ==&lt;br /&gt;
The HUD aggregates application menus in a searchable interface which can be summoned by pressing ALT.  The Web API allows a web application to add its own &amp;quot;menu&amp;quot; entries to the HUD, which can then be searched alongside other application menus.&lt;br /&gt;
&lt;br /&gt;
=== Administrators ===&lt;br /&gt;
The HUD could contain the Site Administation hierachy from the Settings block.&lt;br /&gt;
&lt;br /&gt;
=== Teachers ===&lt;br /&gt;
When on a course page, the HUD could contain the Course Administration links from the Settings block&lt;br /&gt;
&lt;br /&gt;
== Messaging Menu ==&lt;br /&gt;
The Messaging Menu sits in the top-right of the screen, by the clock.  It displays services such as Email, Microblogging etc that the user is connected to, links to write new messages etc, and an indication of new or unread messages for each service.  Webapps allow a web application to add it&#039;s own messaging service to this indicator.&lt;br /&gt;
&lt;br /&gt;
=== All Users ===&lt;br /&gt;
When the Messages block is visible on a page, the Messaging menu could contain an indication of unread messages, with links to view messages and write a new message&lt;br /&gt;
&lt;br /&gt;
== Notifications ==&lt;br /&gt;
Notifications are unobtrusive bubbles that display event notifications from applications (&amp;quot;There are new updates available&amp;quot;, &amp;quot;Your backup has started&amp;quot;, etc.). The Web API allows web application to display their own event notifications.&lt;br /&gt;
&lt;br /&gt;
=== All Users ===&lt;br /&gt;
Create a new [[Messaging consumer]] to enable desktop notifications as an option for receiving notifications when logged in to Moodle.&lt;br /&gt;
&lt;br /&gt;
== Media Menu ==&lt;br /&gt;
The Media Menu contains the global volume control, but can also display details of currently playing media from running media applications, with playback controls.  The Web API allows a web application add these controls.&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Messaging_consumer&amp;diff=34612</id>
		<title>Messaging consumer</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Messaging_consumer&amp;diff=34612"/>
		<updated>2012-08-01T08:19:44Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Redirected page to Messaging consumers&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Messaging consumers]]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Unity_Webapp&amp;diff=34611</id>
		<title>Unity Webapp</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Unity_Webapp&amp;diff=34611"/>
		<updated>2012-08-01T08:12:42Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Ubuntu&#039;s Unity interface has a new &amp;quot;[http://developer.ubuntu.com/api/ubuntu-12.04/javascript/index.html Web API]&amp;quot; feature, allowing web applications to integrate with the desktop.  It would be nice to add some integration to Moodle for Ubuntu users.  This page is to brainstorm possible integrations for various types of user.&lt;br /&gt;
&lt;br /&gt;
== Launcher ==&lt;br /&gt;
The launcher is where the icons for launching applications live.  The Web API allows a web application to have its own icon in the launcher. It also allows a &amp;quot;Quick List&amp;quot; to be displayed on right click allowing quick access to application features (e.g. opening a new document, play/pausing music).&lt;br /&gt;
&lt;br /&gt;
== HUD ==&lt;br /&gt;
The HUD aggregates application menus in a searchable interface which can be summoned by pressing ALT.  The Web API allows a web application to add its own &amp;quot;menu&amp;quot; entries to the HUD, which can then be searched alongside other application menus.&lt;br /&gt;
&lt;br /&gt;
=== Administrators ===&lt;br /&gt;
The HUD could contain the Site Administation hierachy from the Settings block.&lt;br /&gt;
&lt;br /&gt;
=== Teachers ===&lt;br /&gt;
When on a course page, the HUD could contain the Course Administration links from the Settings block&lt;br /&gt;
&lt;br /&gt;
== Messaging Menu ==&lt;br /&gt;
The Messaging Menu sits in the top-right of the screen, by the clock.  It displays services such as Email, Microblogging etc that the user is connected to, links to write new messages etc, and an indication of new or unread messages for each service.  Webapps allow a web application to add it&#039;s own messaging service to this indicator.&lt;br /&gt;
&lt;br /&gt;
=== All Users ===&lt;br /&gt;
When the Messages block is visible on a page, the Messaging menu could contain an indication of unread messages, with links to view messages and write a new message&lt;br /&gt;
&lt;br /&gt;
== Notifications ==&lt;br /&gt;
Notifications are unobtrusive bubbles that display event notifications from applications (&amp;quot;There are new updates available&amp;quot;, &amp;quot;Your backup has started&amp;quot;, etc.). The Web API allows web application to display their own event notifications.&lt;br /&gt;
&lt;br /&gt;
== Media Menu ==&lt;br /&gt;
The Media Menu contains the global volume control, but can also display details of currently playing media from running media applications, with playback controls.  The Web API allows a web application add these controls.&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Unity_Webapp&amp;diff=34610</id>
		<title>Unity Webapp</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Unity_Webapp&amp;diff=34610"/>
		<updated>2012-08-01T08:06:13Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Created page with &amp;quot;Ubuntu&amp;#039;s Unity interface has a new &amp;quot;[http://developer.ubuntu.com/api/ubuntu-12.04/javascript/index.html Web API]&amp;quot; feature, allowing web applications to integrate with the desktop...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Ubuntu&#039;s Unity interface has a new &amp;quot;[http://developer.ubuntu.com/api/ubuntu-12.04/javascript/index.html Web API]&amp;quot; feature, allowing web applications to integrate with the desktop.  It would be nice to add some integration to Moodle for Ubuntu users.  This page is to brainstorm possible integrations for various types of user.&lt;br /&gt;
&lt;br /&gt;
== Launcher ==&lt;br /&gt;
The launcher is where the icons for launching applications live.  The Web API allows a web application to have its own icon in the launcher. It also allows a &amp;quot;Quick List&amp;quot; to be displayed on right click allowing quick access to application features (e.g. opening a new document, play/pausing music).&lt;br /&gt;
&lt;br /&gt;
== HUD ==&lt;br /&gt;
The HUD aggregates application menus in a searchable interface which can be summoned by pressing ALT.  The Web API allows a web application to add its own &amp;quot;menu&amp;quot; entries to the HUD, which can then be searched alongside other application menus.&lt;br /&gt;
&lt;br /&gt;
== Messaging Menu ==&lt;br /&gt;
The Messaging Menu sits in the top-right of the screen, by the clock.  It displays services such as Email, Microblogging etc that the user is connected to, links to write new messages etc, and an indication of new or unread messages for each service.  Webapps allow a web application to add it&#039;s own messaging service to this indicator.&lt;br /&gt;
&lt;br /&gt;
== Notifications ==&lt;br /&gt;
Notifications are unobtrusive bubbles that display event notifications from applications (&amp;quot;There are new updates available&amp;quot;, &amp;quot;Your backup has started&amp;quot;, etc.). The Web API allows web application to display their own event notifications.&lt;br /&gt;
&lt;br /&gt;
== Media Menu ==&lt;br /&gt;
The Media Menu contains the global volume control, but can also display details of currently playing media from running media applications, with playback controls.  The Web API allows a web application add these controls.&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=ilp_block&amp;diff=34113</id>
		<title>ilp block</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=ilp_block&amp;diff=34113"/>
		<updated>2012-06-07T09:45:42Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: Corrected documentation of entry_specific_process_data&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This page contains some documentation of writing plugins for the Moodle 2 version of the ILP block.&lt;br /&gt;
This is a work in progress and is currently being authored by a third-party developer, not a member of the team who developed the block and its plugin architecture, so please bear that in mind when reading this.  The information here is based on a discussion and demonstration with the developers, plus some of my own research and assumptions.&lt;br /&gt;
&lt;br /&gt;
== Plugin Types ==&lt;br /&gt;
The ILP block provides a framework for recording reports regarding students, displaying them, and reporting on other student data. It also provides a set of plug-ins to this framework.  The current plug-in types include:&lt;br /&gt;
* Form Elements - These are the different elements that can be added to reports&lt;br /&gt;
* Dashboard Elements - These are used to display content on the student dashboard&lt;br /&gt;
* Dashboard Tabs - These display the main content of the ILP&lt;br /&gt;
* MIS - These provide links to MIS data (?)&lt;br /&gt;
* Dashboard Templates - These control the appearance and layout of the student dashboard&lt;br /&gt;
Note that these are *not* Moodle plugins in the sense that you may be used to.  The ILP&#039;s plugin architecture is completely seperate from that of Moodle.&lt;br /&gt;
&lt;br /&gt;
== Writing Plugins ==&lt;br /&gt;
A good place to start is by modifying an existing plugin of the same type as the one you want to create.&lt;br /&gt;
&lt;br /&gt;
=== Form Elements ===&lt;br /&gt;
A form element consists of 2 files, which live in blocks/ilp/classes/form_elements/plugins:&lt;br /&gt;
==== ilp_element_plugin_{plugin_name}.php ====&lt;br /&gt;
* The file must include blocks/ilp/classes/form_elements/ilp_element_plugin.php, as this file defines ilp_element_plugin &amp;lt;code php&amp;gt;require_once($CFG-&amp;gt;dirroot.&#039;/blocks/ilp/classes/form_elements/ilp_element_plugin.php&#039;);&amp;lt;/code&amp;gt;&lt;br /&gt;
* This file defines a class called ilp_element_plugin_{plugin_name} as an extension of ilp_element_plugin.&lt;br /&gt;
&amp;lt;code php&amp;gt;class ilp_element_plugin_text_area extends ilp_element_plugin {&lt;br /&gt;
&lt;br /&gt;
	public $tablename;&lt;br /&gt;
	public $data_entry_tablename;&lt;br /&gt;
	public $minimumlength;		//defined by the form creator to validate user input&lt;br /&gt;
	public $maximumlength;		//defined by the form creator to validate user input&amp;lt;/code&amp;gt;&lt;br /&gt;
* The parent class will automatically provide an interface to the ILP&#039;s database via $this-&amp;gt;dbc, and to the database manager via $this-&amp;gt;dbman. &lt;br /&gt;
* The constuctor must set $this-&amp;gt;tablename and $this-&amp;gt;data_entry_tablename.  These are 2 tables that will be created in the database to store the configuration for each instance of the plugin (when it is added to a report), and the data for each entry using the plugin (when someone fills in a report that uses the plugin), respectively. &amp;lt;code php&amp;gt;    function __construct() {&lt;br /&gt;
    	$this-&amp;gt;tablename = &amp;quot;block_ilp_plu_are&amp;quot;;&lt;br /&gt;
    	$this-&amp;gt;data_entry_tablename = &amp;quot;block_ilp_plu_are_ent&amp;quot;;&lt;br /&gt;
    	&lt;br /&gt;
    	parent::__construct();&lt;br /&gt;
    }&amp;lt;/code&amp;gt;&lt;br /&gt;
* TODO: document the load() method&lt;br /&gt;
* The install() method allows you to add fields and keys to the tables defined by the constructor.  There is no install.xml for ILP plugins, so you need to use the [[Data definition API]] to do this.  You should find that you can still use the [[XMLDB editor]] to define your tables, then use the PHP code it genreates for upgrade scripts to write this method for you. Remember to replace any generated $dbman calls with $this-&amp;gt;dbman.&lt;br /&gt;
* The uninstall() method lets you run code to drop tables or data as necessary to remove the plugin from the database.&lt;br /&gt;
* The audit_type() method should return a string that&#039;s used to identify the plugin type in logs etc. &amp;lt;code php&amp;gt;public function audit_type() {&lt;br /&gt;
    return get_string(&#039;ilp_element_plugin_text_area_type&#039;,&#039;block_ilp&#039;);&lt;br /&gt;
}&amp;lt;/code&amp;gt;&lt;br /&gt;
* The language_strings() method takes the place of the lang file (I&#039;m not sure how/if this supports localisation).  The $string array is passed the function as a reference, the strings are added to the array, and then $string is returned. The strings are then accessible as part of the block_ilp component, and their identifiers should be prefixed with the full plugin name &amp;lt;code php&amp;gt; function language_strings(&amp;amp;$string) {&lt;br /&gt;
        $string[&#039;ilp_element_plugin_text_area&#039;] 		= &#039;Textarea&#039;;&lt;br /&gt;
        $string[&#039;ilp_element_plugin_text_area_type&#039;] 	= &#039;Textarea&#039;;&lt;br /&gt;
        $string[&#039;ilp_element_plugin_text_area_description&#039;] = &#039;A textarea&#039;;&lt;br /&gt;
        ...&lt;br /&gt;
        &lt;br /&gt;
        return $string;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* delete_form_element() defines the code required to delete an instance of the field from a report &amp;lt;code php&amp;gt;public function delete_form_element($reportfield_id) {&lt;br /&gt;
    	return parent::delete_form_element($this-&amp;gt;tablename, $reportfield_id);&lt;br /&gt;
    }&amp;lt;/code&amp;gt;&lt;br /&gt;
* entry_form() adds the fields to report form, using the [[lib/formslib.php_Form_Definition|Form Definition API]].  The $mform object is passed to the function, which then adds elements to the form as it would in the form class&#039;s own definition() method.&lt;br /&gt;
* entry_process_data() is passed the data submitted to the plugin&#039;s fields for any processing that needs to be done before it is inserted into the database.  Simple elements can just use the parent method which will create or update a record in the plugin&#039;s data table. More complex elements can hand of the processing to entry_specific_process_data, which can do specialist processing.&amp;lt;code php&amp;gt; public function entry_process_data($reportfield_id,$entry_id,$data) {&lt;br /&gt;
    return $this-&amp;gt;entry_specific_process_data($reportfield_id,$entry_id,$data); 	&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
public function entry_specific_process_data($reportfield_id, $entry_id, $data) {&lt;br /&gt;
    // do processing&lt;br /&gt;
}&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== ilp_element_plugin_{plugin_name}_mform.php ====&lt;br /&gt;
This file defines the form used to adding an instance of the field to a report.  Think of it as the form for creating and editing entries in the $tablename table you defined in the ilp_element_plugin_{plugin_name}.php file.&lt;br /&gt;
* The file must start by including blocks/ilp/classes/form_elements/ilp_element_plugin_mform.php, which defines the ilp_element_plugin_mform class.&amp;lt;code php&amp;gt;require_once($CFG-&amp;gt;dirroot.&#039;/blocks/ilp/classes/form_elements/ilp_element_plugin_mform.php&#039;);&amp;lt;/code&amp;gt;&lt;br /&gt;
* The file defines a class called ilp_element_plugin_{plugin_name}_mform, which extends ilp_element_plugin_mform &amp;lt;code php&amp;gt;class ilp_element_plugin_text_area_mform  extends ilp_element_plugin_mform {&amp;lt;/code&amp;gt;&lt;br /&gt;
* The class has 3 abstract methods which must be overridden:&lt;br /&gt;
** specific_definition() allows you to add fields to the form.  It will already have a fields to set the label, description, and whether the field is required.  You can add any other fields using the [[lib/formslib.php_Form_Definition|Form Definition API]]. It recieves $mform as its only arugument&lt;br /&gt;
** specific_validation() allows you define validation for the fields added by specific_definition(). It works just like the standard moodleform [[lib/formslib.php_Validation|validation]]() method.&lt;br /&gt;
** specific_process_data() recieves the $data object as its only arugment, and allows you to do any processing required for your fields.&lt;br /&gt;
&lt;br /&gt;
=== Dashboard Tabs ===&lt;br /&gt;
A dashboard tab defines consits of a PHP class with an optional HTML template and some Javascript. The files live in blocks/ilp/classes/dashboard/tabs&lt;br /&gt;
==== ilp_dashboard_{pluginname}_tab ====&lt;br /&gt;
* This file starts by including ilp_dashboard_tab.php which defines the ilp_dashboard_tab class &amp;lt;code php&amp;gt;require_once($CFG-&amp;gt;dirroot.&#039;/blocks/ilp/classes/dashboard/ilp_dashboard_tab.php&#039;);&amp;lt;/code&amp;gt;&lt;br /&gt;
* Then a class is defined called ilp_dashboard_{pluginname}_tab, which extends ilp_dashboard_tab &amp;lt;code php&amp;gt;class ilp_dashboard_reports_tab extends ilp_dashboard_tab {&amp;lt;/code&amp;gt;&lt;br /&gt;
* The constructor recieves the student ID and course ID as arguments.&lt;br /&gt;
* The display_name() method returns the string to display on the tab itself &amp;lt;code php&amp;gt;function display_name()    {&lt;br /&gt;
    return    get_string(&#039;ilp_dashboard_reports_tab_name&#039;,&#039;block_ilp&#039;);&lt;br /&gt;
}&amp;lt;/code&amp;gt;&lt;br /&gt;
* define_second_row() allows a second row of tabs to be defined. The row should be defined in $this-&amp;gt;second_row as an array of arrays containing an id, link and name for each second row tab. Read the comments in an existing example.&amp;lt;code php&amp;gt;function define_second_row()    {&lt;br /&gt;
...&lt;br /&gt;
    // the tabitem and selectedtab query string params are added to the linkurl in the&lt;br /&gt;
    // second_row() function&lt;br /&gt;
    $this-&amp;gt;secondrow[]    =    array(&#039;id&#039;=&amp;gt;$r-&amp;gt;id,&#039;link&#039;=&amp;gt;$this-&amp;gt;linkurl,&#039;name&#039;=&amp;gt;$r-&amp;gt;name);&lt;br /&gt;
...&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* define_third_row() tab allows for a third row of tabs, if you really want&lt;br /&gt;
* display() returns the content of the tab as a string of HTML.  If you want, you can use an HTML template defined in ilp_dashboard_{pluginname}_tab.html by setting up the variables it requires, including it and capturing the output with an output buffer, then returning it.  Alternatively, you could use Moodle&#039;s [Output API] to generate the HTML to be returned.  It recieves the $selectedtab as its only argument.&lt;br /&gt;
* language_strings() functions in the same way as for Form Elements, above.&lt;br /&gt;
* config_form() recieves an $mform as its only arugment, to which you can add fields to be displayed on the screen where the tab can be enabled or disabled.&lt;br /&gt;
* Additional methods can be defined here as required for use within the class.&lt;br /&gt;
* A Javascript module can be defined in ilp_dashboard_{pluginname}_tab.js and a $PAGE-&amp;gt;requires_js_init_call added to the display() method to initialise it.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==iLP database table relationships==&lt;br /&gt;
[http://video.cant-col.ac.uk/ilp/iLP-DB-relationships.pdf iLP-DB-relationships.pdf]&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User:Mark_Johnson/Mforms_and_AJAX&amp;diff=33997</id>
		<title>User:Mark Johnson/Mforms and AJAX</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User:Mark_Johnson/Mforms_and_AJAX&amp;diff=33997"/>
		<updated>2012-05-24T07:58:49Z</updated>

		<summary type="html">&lt;p&gt;Marxjohnson: /* Displaying the form */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;With a little bit of trickery, it&#039;s possible to load, display and sumbit a Moodle Form (mform) defined using the [[Form API]] entirely via AJAX, without having to reload the page.&lt;br /&gt;
&lt;br /&gt;
Please note that this page isn&#039;t a full step-by-step example, it just shows you an overview of the steps that are necessary.  You&#039;ll need a working knowledge of [[AJAX]] and Moodle&#039;s [[Javascript]] API to make use of it.&lt;br /&gt;
&lt;br /&gt;
The process works in two steps:&lt;br /&gt;
# Loading and displaying the form&lt;br /&gt;
# Submitting and processing the form, then updating the page&lt;br /&gt;
The second set isn&#039;t reliant on the first step, so it&#039;s possible to have page that displays a form in the ususal manner, then submits it via AJAX. The solution consists of a server-side script to output and process the form (both steps use this script), and a couple of javascript functions to display and submit the form.&lt;br /&gt;
&lt;br /&gt;
==Displaying the form==&lt;br /&gt;
To start with, you&#039;ll need a PHP script that looks much like a normal script used to display an mform, with a few modifications.&lt;br /&gt;
Note that this is a &#039;&#039;&#039;seperate&#039;&#039;&#039; script to the one for the page on which you&#039;re displaying the form.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
require_once(&#039;config.php&#039;);&lt;br /&gt;
require_once(&#039;form_class.php&#039;);&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
// Check permissions etc&lt;br /&gt;
...&lt;br /&gt;
// Get record for editing&lt;br /&gt;
$record = $DB-&amp;gt;get_record(&#039;table&#039;, $params);&lt;br /&gt;
...&lt;br /&gt;
// Initialise the form class&lt;br /&gt;
$form = new form_class();&lt;br /&gt;
&lt;br /&gt;
if ($record) {&lt;br /&gt;
    $form-&amp;gt;set_data($record);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
if ($data = $form-&amp;gt;get_data()) {&lt;br /&gt;
  // Process the submission and save data to the database&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The end of the script will look a bit different to normal. Rather than displaying a page containing the form, we just want to capture the HTML for the form&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
ob_start();&lt;br /&gt;
$form-&amp;gt;display();&lt;br /&gt;
$formhtml = ob_get_clean();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Now there&#039;s a slightly trickier bit.  The form API has built-in javascript validation, an in addition we might have our own javascript which needs to be initialised after the form is displayed.  For this, we need to generate the javascript using the [[Output API]] and rip out the bits we need:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// First we get the script generated by the Form API&lt;br /&gt;
if (strpos($formhtml, &#039;&amp;lt;/script&amp;gt;&#039;) !== false) {&lt;br /&gt;
    $outputparts = explode(&#039;&amp;lt;/script&amp;gt;&#039;, $formhtml);&lt;br /&gt;
    $html = $outputparts[1];&lt;br /&gt;
    $script = str_replace(&#039;&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;&#039;, &#039;&#039;, $outputparts[0]);&lt;br /&gt;
} else {&lt;br /&gt;
    $html = $formhtml;&lt;br /&gt;
}&lt;br /&gt;
// Next we get the M.yui.loader call which includes the Javascript libraries&lt;br /&gt;
$headcode = $PAGE-&amp;gt;requires-&amp;gt;get_head_code($PAGE, $OUTPUT);&lt;br /&gt;
$loadpos = strpos($headcode, &#039;M.yui.loader&#039;);&lt;br /&gt;
$cfgpos = strpos($headcode, &#039;M.cfg&#039;);&lt;br /&gt;
$script .= substr($headcode, $loadpos, $cfgpos-$loadpos);&lt;br /&gt;
// And finally the initalisation calls for those libraries&lt;br /&gt;
$endcode = $PAGE-&amp;gt;requires-&amp;gt;get_end_code();&lt;br /&gt;
$script .= preg_replace(&#039;/&amp;lt;\/?(script|link)[^&amp;gt;]*&amp;gt;/&#039;, &#039;&#039;, $endcode);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
We&#039;ve now got the HTML of the form, and the Javascript that goes with it. We just need to send it back to the client.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
echo json_encode(array(&#039;html&#039; =&amp;gt; $html, &#039;script&#039; =&amp;gt; $script));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s the server side done for now. Let&#039;s look at the code for displaying and posting the form (using YUI3). Your Javascript module will require the following YUI modules: base, node, io, json&lt;br /&gt;
&lt;br /&gt;
In out Javascript module, we&#039;ll have a function that looks something like this. It can be triggered on load, off a button press, or whatever:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
display_form: function() {&lt;br /&gt;
    Y = this.Y;&lt;br /&gt;
    Y.io(M.cfg.wwwroot+&#039;/form_ajax.php&#039;, {&lt;br /&gt;
       on : {&lt;br /&gt;
           success: function(id, o) {&lt;br /&gt;
               response = Y.JSON.parse(o.responseText)&lt;br /&gt;
               form = Y.Node.create(response.html); // Create a Node from the HTML&lt;br /&gt;
               Y.one(&#039;#form-container&#039;).setContent(form) // Display the form on the page before we do anything with javascript, since the javascript will expect the form elements to already be on the page&lt;br /&gt;
&lt;br /&gt;
               scriptel = document.createElement(&#039;script&#039;); // Create a &amp;lt;script&amp;gt; tag&lt;br /&gt;
               scriptel.textContent = response.script; // Put the Javascript inside the script tag&lt;br /&gt;
               document.body.appendChild(scriptel); // Add the script tag to the page.&lt;br /&gt;
           }&lt;br /&gt;
       } &lt;br /&gt;
    });&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Node that when you do document.body.appendChild(scriptel), the javascript will be executed immediately, meaning any libraries will be included, defined functions will be come available, and init functions will be run.&lt;br /&gt;
&lt;br /&gt;
==Submitting the Form==&lt;br /&gt;
To submit the form, we&#039;ll serialise the form data using YUI, and send it via a second AJAX request. For this, your Javascript will need the additional YUI module io-form&lt;br /&gt;
&lt;br /&gt;
You&#039;ll need to create the following Javascript function, and attach it to run when the form&#039;s submit button is clicked:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
submit_form: function(e) {&lt;br /&gt;
    var Y = this.Y;&lt;br /&gt;
&lt;br /&gt;
    // Stop the form submitting normally&lt;br /&gt;
    e.preventDefault();&lt;br /&gt;
&lt;br /&gt;
    // Form serialisation works best if we get the form using getElementById, for some reason&lt;br /&gt;
    var form = document.getElementById(&#039;form_id&#039;);&lt;br /&gt;
    // If your form has a cancel button, you need to disable it, otherwise it&#039;ll be sent with the request&lt;br /&gt;
    // and Moodle will think your form was cancelled&lt;br /&gt;
    Y.one(&#039;#id_cancel&#039;).set(&#039;disabled&#039;, &#039;disabled&#039;);&lt;br /&gt;
&lt;br /&gt;
    // Send the request&lt;br /&gt;
    Y.io(M.cfg.wwwroot+&#039;/form_ajax.php&#039;, {&lt;br /&gt;
        method: post,&lt;br /&gt;
        on: {&lt;br /&gt;
            success: function(id, o) {&lt;br /&gt;
                response = Y.JSON.parse(o.responseText);&lt;br /&gt;
                // Display some feedback to the user&lt;br /&gt;
            }&lt;br /&gt;
        },&lt;br /&gt;
        form: form,&lt;br /&gt;
        context: this&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
}&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Finally, we need to a revisit the PHP script to provide some feedback for the user. Instead of redirecting to a confirmation page as you would normally do, you could return a message to display to the user. If the set of records which you&#039;ve just added to is displayed on the page, my preferred approach is to generate the HTML for those records again and send it back, so the page can be updated on-the-fly:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
...&lt;br /&gt;
if ($data = $form-&amp;gt;get_data()) {&lt;br /&gt;
    // Process and save data&lt;br /&gt;
&lt;br /&gt;
    $data = my_plugin_get_data(..., ...);&lt;br /&gt;
    $renderer = $PAGE-&amp;gt;get_renderer(&#039;my_plugin&#039;);&lt;br /&gt;
    $html = $renderer-&amp;gt;record_list($data);&lt;br /&gt;
    echo json_encode(array(&#039;output&#039; =&amp;gt; $html));&lt;br /&gt;
    exit();&lt;br /&gt;
}&lt;br /&gt;
...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Marxjohnson</name></author>
	</entry>
</feed>