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