Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Moodle Mobile 2 (Ionic 1): Difference between revisions

From MoodleDocs
No edit summary
No edit summary
Line 1: Line 1:
<p class="note">For user documentation see '''[[:en:Moodle Mobile|Moodle Mobile]]'''.</p>
<p class="note">For user documentation see '''[[:en:Moodle Mobile|Moodle Mobile]]'''.</p>
{{Moodle Mobile 2 (Ionic 1)}}
{{Moodle Mobile 2 (Ionic 1)}}
{{Moodle Mobile Classic}}


{{obsolete}}
{{obsolete}}

Revision as of 11:31, 11 January 2019

For user documentation see Moodle Mobile.


Warning: This page is no longer in use. The information contained on the page should NOT be seen as relevant or reliable.


This template is unused and can be deleted.


Warning: This page is no longer in use. The information contained on the page should NOT be seen as relevant or reliable.

IMPORTANT: This documentation will be obsolete in May 2018, please read this announcement.

General Overview

Moodle Mobile 2 (MM2) is the official mobile app for Moodle. MM2 is an HTML5 app that uses the Ionic hybrid apps framework. Former Moodle Mobile versions (1.x) used jQuery and Backbone (see Moodle Mobile 1 for documentation about the former app).

Basically, the app is a Web Services client that uses REST as protocol to obtain and send information to your Moodle installation/s.

Interaction with the phone and compiling/packaging is done using the Cordova framework (also commonly known as Phonegap).

MM1 plugins are not going to work in MM2.

Requirements

  • Moodle 2.4
  • Mobile services enabled (Administration -> Plugins -> Web Services -> Mobile)

Future versions will require the last minor versions if Web Services are not back ported.

Architecture

MM2 is built with ionic framework, ionic is a full stack mobile hybrid apps that uses AngularJS framework.

AngularJS is a structural framework for dynamic web apps. It lets you use HTML as your template language and lets you extend HTML's syntax to express your application's components clearly and succinctly.

AngularJS is quite popular, you may find lot of sites with updated documentation and free courses, the same applies for Ionic.


Ionic / AngularJS

The app architecture is the typical for an AngularJS app. We use controllers, factories, directives, providers, etc..

The app is designed in a modular way, most of the functionalities are AngularJs modules that implements plugins (in a similar way to Moodle plugins).

There are a set of core services for handling authentication, configuration, plugin management, cache, etc… and specific modules for tasks like internationalization.

Most of the services are implemented as factories.

The app uses AngularJs logging to create an application log (only in developer mode).


App structure and naming conventions

The app is structured in two parts: core and addons.

In core we have the code that the app needs to be able to work (basic version). There are services, components, directives, filters, etc.

The components inside core are similar to addons, but the app needs them to work: login, sidemenu, courses, etc. Each component needs to define a main.js file to initialize it and to define the module.

Naming conventions for core:

  • The module name for components needs to be mm.core.componentname and it should be defined in the component’s main.js. The services, filters and directives that don’t belong to a component need to use the module mm.core (defined in www/core/main.js).
  • All the services names need to start with $mm, followed by the service name in camel-case. For example, $mmSitesManager.
  • The filters and directives use camel-case starting with mm. For example, mmFormatText.
  • The controllers names need to start with mm (without dollar), followed by the controller name in camel-case. The controller name needs to contain Ctrl to easily identify it as a controller. For example, mmCourseListCtrl.

In addons we have the official MM2 addons. Each addon can have services, controllers, templates, directives, filters, styles and lang files. The addon needs to specify a main.js file to initialize the addon and to register itself into one or more delegates (this determines where will the plugin be shown).

Naming conventions for addons:

  • The module name for addons needs to be mm.addons.addonname and it should be defined in the addon's main.js.
  • All the services names inside an addon need to start with $mma, followed by the addon name and the service name in camel-case. For example, $mmaMessagesHandlers.
  • The filters and directives inside an addon use camel-case starting with mma followed by the addon name and the directive/filter name. For example, mmaMessagesFormat.
  • The controllers names need to start with mma (without dollar), followed by the addon name and the controller name in camel-case. The controller name needs to contain Ctrl to easily identify it as a controller. For example, mmaForumListCtrl.

Naming conventions for subaddons:

Subaddons match Moodle subplugins or subsystems (like question behaviours, question types or quiz access rules).

  • The module name for subaddons needs to be mm.addons.addonname_subaddonname and it should be defined in the addon's main.js.
  • The rest of the conventions are the same that for addons but adding the subaddon name to the addon name. See www/addons/qtype or www/addons/qbehaviour for examples

Internationalization

The app is translated to several languages, we use a module called angular-translate that supports lazy-loading of languages and pluralization. All the languages files are shipped with the app except the ones for remote addons.

To load the language files we use angular-translate-loader-partial, a loader developed by angular-translate authors that lets you load “parts” of the translation table. In MM 2.0 we’ll only have a single language file under www/build/lang, but with this loader we should be able to add remote plugins lang files too when they are implemented.

Each plugin/component needs to define its own lang files if they need any. These files need to be placed inside a lang folder and must be named languagecode.json, where languagecode is the ISO code of the language (i.e. en.json). The structure of those files must be the following:

{
   “langkey””: “The string to be shown”
}

All the lang files are merged into a single lang file per language inside www/build/lang. In this merge process, the strings are prefixed to prevent naming collisions. The prefixes are:

  • Core strings (www/core/lang): mm.core.*
  • Component strings (www/core/components/mycomponent/lang): mm.mycomponent.*
  • Addons strings (www/addons/myaddon/lang): mma.myaddon.*

For example, if the example lang file from above was inside the folder www/core/courses/component/lang, the merged file would have an entry like this:

“mm.courses.langkey”: “The string to be shown”

To use the language string, the prefixed version must be used. For example, to use the langkey string from the courses component we need to do this:

$translate('mm.courses.langkey’) - Returns a Promise
{{ 'mm.courses.langkey’ | translate}} - Used in a view, shows the translated string.

Angular-translate is initialized inside www/core/lib/lang.js, where we set English as the fallback language and we load the current language based on the user app settings and the device locale. Notice that locales like “en-uk” or “es-mx” will be treated as “en” or “es” if that translation is not found.

Storage

The app needs to store some information in the mobile device: the current site information, the list of recorded sites, a cache of the REST calls, etc…

For app storage we use the ydn-db javascript library, it supports storage in IndexedDB, WebSQL and localStorage implementing polyfills, promises, automatic storage system detection, etc...

We have two levels of databases: the app database (1 DB for the whole app) and the site databases (1 DB per site). All the data that is site-related is stored in the site database, so it’s easier to retrieve and delete (when a site is deleted).

The app DB is defined inside www/core/lib/app.js. This file implements a provider with the functions $mmAppProvider.registerStore and $mmAppProvider.registerStores to add new stores to the app DB.

The site DB is defined inside www/core/lib/sitesfactory.js. This factory manages the creation and delete of the sites DB, creating a DB per site.

IMPORTANT: Modifying the schema of an already existing store deletes all its data in WebSQL Storage (in IndexedDB the data is kept). If a store schema needs to be modified, the data should be manually migrated to the new store.

Styles

./scss/app.scss The main SCSS file which a themer should be using to customise the look and feel of the app. That will also be the file we start from to make colour palettes. As core developers we will use this app to override the core ionic variables, as well as modifying existing ionic styles.

./www/core/scss This directory holds the styling components we want to offer to our themers. The variables associated with those components should be held in the same files and flagged as !default to allow themers to override them. We should not override those variables in ./scss/app.scss because default values should be provided by us and work with our core app style.

./www/core/components/X/scss & ./www/core/addons/X/scss Those directories will contain styles that are specific and should not be re-use by other components/addons. The same goes with the variables from those addons, they should not be re-used from another component/addon.

To prevent naming collisions between SCSS files, the classes and IDs should be prefixed. The naming conventions for those prefixes are similar to the rest of Moodle Mobile 2 prefixes:

  • Core styles should be prefixed with mm-. For example, .mm-split-pane-menu.
  • Components styles should be prefixed with mm-componentname-. For example, .mm-login-myclass.
  • Addons styles should be prefixed with mma-addonname-. For example, .mma-grades-table.


Cordova

We use Cordova to access the mobile device APIs. We try to use only core Cordova plugins but sometimes that’s impossible. If we have to use additional plugins (like Webintents, local notifications, custom URL schemes) we use only plugins well-maintained, generally those listed here: http://plugins.telerik.com/

For integrating Cordova plugins we use ngCordova; ngCordova is a collection of AngularJS extensions on top of the Cordova API that make it easy to build, test, and deploy Cordova mobile apps with AngularJS. The main feature of ngCordova is that instead callbacks it uses promises, so it integrate very well with AngularJS.


Testing

Tests are written using Jasmine and run using Karma. Karma is a JavaScript command line tool that can be used to spawn a web server which loads your application's source code and executes your tests.

Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.


Addons

The app comes with a predefined set of addons: messages, course contents, notifications, etc. All these addons are inside the www/addons folder. The structure of the files inside an addon is explained in the section App structure and naming conventions.

An addon can be shown in several places. The app has one delegate per each place an addon can be shown, so each addon can register itself to any set of delegates.

The delegates to display data in certain places are:

  • $mmSideMenuDelegate: The addons registered in here will be shown in the app’s side menu.
  • $mmCoursesDelegate: The addons registered in here will be shown under each course in the course list.
  • $mmCourseDelegate: The addons registered in here need to define which module they handle (forum, assign, book, ...). When a user opens a certain module, the addon registered for that module type will be used. There can only be one addon per module type.
  • $mmUserDelegate: The addons registered in here will be shown in the user profile page.
  • $mmSettingsDelegate: The addons registered in here will be shown in App Settings.
  • $mmPluginFileDelegate: The addons registered in here will be able to replace plugin file urls to remove revision number and improve performance when downloading them.

Delegates related to Moodle subsystems or subplugins that in the app will be implemented as subaddons:

  • $mmaModQuizAccessRulesDelegate: You can use this service to register your own access rules handlers to be used in a quiz. Addons should be placed in www/addons/mod_quiz/accessrules/
  • $mmQuestionBehaviourDelegateProvider: You can use this provider to register your own question behaviour handlers to be used in a quiz or other places. Addons should be placed in www/addons/qbehaviour/
  • $mmQuestionDelegateProvider: You can use this provider to register your own question handlers to be used in a quiz or other places. Addons should be placed in www/addons/qtype/
  • $mmaModAssignFeedbackDelegate: You can use this provider to register your assign feedback plugins to be used in an assignment. Addons should be placed in www/addons/mod/assign/feedback
  • $mmaModAssignSubmissionDelegate: You can use this provider to register your assign submission plugins to be used in an assignment. Addons should be placed in www/addons/mod/assign/submission
  • $mmUserProfileFieldsDelegate: You can use this provider to register your user profile fields to be used in user profile. Addons should be placed in www/addons/userprofilefield
  • $mmaMessageOutputDelegate: You can use this service to register message output processors to be used in places like notification preferences. Addons should be placed in www/addons/messageoutput/

Finally, the app has some other delegates you can use:

  • $mmInitDelegate: You can register processes to be executed while the app is being initialized. It cannot be used from remote addons since they're executed after the init process has ended.
  • $mmContentLinksDelegate: The addons registered in here will receive content URLs (from notifications or links clicked) and they need to define which actions can be done for that URL. In the case of links clicked, the first action returned will be automatically triggered. You can use this delegate to intercept clicks on certain URLs and make them redirect to your addon.
  • $mmPushNotificationsDelegate: You can use this service to be notified when a push notification is clicked.
  • $mmCoursePrefetchDelegate: You should use this provider if your module addon should be downloadable.
  • $mmFileUploaderDelegate: You can use this provider to add new ways to upload a file.
  • $mmCronDelegate: You can use this service to register tasks that should be executed periodically.
  • $mmURLDelegate: You can use this service to be notified when the app is launched using the Custom URL Scheme cordova plugin.

Log

We use angular’s $log service to log app messages.

A decorator has been applied to $log to add the current date and time and the component to the $log service. If the following instruction is used:

$log.debug(“My message”)

A message like the following is written in the log:

4/27/2015, 11:31:33 AM : My message

To add the component to the message, the following must be done before using $log:

$log = $log.getInstance(“Component”)

From now on the $log messages will have the component, so using the previous $log.debug instruction would print the following now:

4/27/2015, 11:31:33 AM Component: My message

Log can be enabled and disabled. The default value for this is defined in the constant mmCoreDebugDefault.

The log can be enabled/disabled in the app no matter the value of mmCoreDebugDefault. To do so, the following instruction needs to be used:

$mmLog.enabled(true/false)

That change is permanent, so if the app is closed it’s going to remember if the log was enabled or disabled.

There are 5 levels of messages:

  • debug: To write messages for debug purposes. It’s the most used in the app.
  • error: To log error messages.
  • warn: To log warnings (for example, it is used to warn that the Moodle version should be updated).
  • log: To log a generic message. Not used.
  • info: To write an info message.

Events

MoodleMobile has a $mmEvents service that allows sending and listening to events in the app. There are two type of events:

  • Unique: Thrown only once in the app's lifetime. If a listener is set after the event has been thrown, its callback is going to be called immediately.
  • Regular: Can be thrown several times in the app's lifetime. Listeners callbacks will only be called when the event is triggered.

These are the several regular events defined in the app:

  • mmCoreEventSessionExpired: Thrown when the user token expires. Param: a string with the site id that expired.
  • mmCoreEventLogin: Thrown when the user logins in the app. It is also thrown when the app is started and the user session is restored.
  • mmCoreEventLogout: Thrown when the user logouts (go to sites screen).
  • mmCoreEventLanguageChanged: Thrown when the language is changed.
  • mmCoreEventSiteAdded: Thrown when a new site is added.
  • mmCoreEventSiteUpdated: Thrown when a site's data is updated. Params: a string with the site id that was updated.
  • mmCoreEventSiteDeleted: Thrown when a site is deleted. Params: site deleted.
  • mmCoreEventQueueEmpty: Thrown when the filepool queue is empty.
  • Filepool file: Thrown when a filepool file is downloaded or its download fails. The event name is unique for each file. Params: an object with success property indicating if the download was a success or not.
  • mmCoreEventCompletionModuleViewed: Thrown when a module with completion enabled is viewed. Params: courseid.
  • mmCoreEventKeyboardShow: Thrown when the keyboard is shown. Params: same as 'native.keyboardshow' event from ionic-plugin-keyboard.
  • mmCoreEventKeyboardHide: Thrown when the keyboard is hidden. Params: same as 'native.keyboardhide' event from ionic-plugin-keyboard.
  • mmCoreEventUserDeleted: Thrown when we detect that a user has been deleted. Params: an object with siteid and the params sent to the WS call that returned the deleted user error.
  • mmCoursesEventMyCoursesUpdated: Thrown when the my courses list should be updated. Params: site id.
  • mmaModForumNewDiscussionEvent: Thrown when a discussion is created in the app. Params: forum id, new discussion id and forum cmid.
  • mmCoreEventPackageStatusChanged: Thrown when the status of a package changes (e.g. a module). Params: siteid, component, componentId and new status.
  • mmCoreEventSectionStatusChanged: Thrown when the status of a course section changes. Params: siteid and sectionid.

Core directives

MoodleMobile has a set of core directives that can be used by any addon or component. These are some of them.

mmAutoFocus

Sometimes it's useful to autofocus some input element when a view is loaded, showing the device's keyboard. You just have to add a mm-auto-focus attribute to your input element and this directive will handle it.

mmBrowser

Links that are not part of the app (e.g. not buttons) should be opened in a separate browser or app, depending on the platform they are being opened with. A directive was created to make this easy:

mm-browser

This will catch a click on the link and open it as it should, whether it opens a file, a URL, etc…

Please note that the directive mm-format-text automatically applies mm-browser to all the links found.

Since Moodle Mobile 2.9 the directive mmBrowser uses $mmContentLinksDelegate to detect if the URL belongs to any addon.

mmCompletion

This directive is meant for activity completion but it can probably be adapted to other uses.

It will display a checkbox showing the current status of the activity. It allows to manually change the status by clicking on it if the activity is configured to do so.

mmExternalContent

External content is anything that is not locally accessible by the app. If some content has to be made available offline, or saved locally for performance reason, you should be using $mmFilepool. This factory allows you to download files locally, add them to a queue to be downloaded asynchronously, invalidate the local copy, etc…

You can also use the directive mm-external-content which will automatically do the job of adding the files to the queue if they are not find locally.

Please note that at the moment it was decided that mm-external-content would ony work for content that is coming directly from Moodle (pluginfile.php), though it is possible for a developer to be using $mmFilepool with any kind of URLs.

mmFile

There are several places throughout the app where the user can see a list of files that can be opened or prefetched. This is what this directive is for.

It requires the name and URL of the file, and it will display a list item with the file's name and icon, along with a button to prefetch or refresh the file. This row can be clicked to open the file.

Please notice that these files won't be downloaded unless the user opens or prefetches them.

mmFormatText

Some of the texts returned by Moodle need to be treated before being rendered since they can include filters, URLs, HTML, etc. You should apply this directive to any text liable to have these.

You just have to wrap the text to format with this directive, something like this:

   <mm-format-text>{{ content }}</mm-format-text>

mmFormatText accepts several parameters to define its behaviour. These are the most important:

  • watch: Set it to true if the variable's value could change after the view is rendered. If you set it to false or you don't set it and the variable's value change then the changes won't be reflected in the view.
  • after-render: Function to be called when the contents are rendered.
  • clean: True if all HTML tags should be removed.
  • singleline: True if new lines should be removed (all the text in a single line). Only valid if clean=true.
  • shorten: To shorten the text. If a number is supplied then it will shorten the text to that number of characters. If a percentage is supplied then the number of characters to short will be the percentage of element's width. E.g. 50% of an element with 1000px width = 500 characters. Default value is 30%.
  • expand-on-click: True if content should be expanded on click (undo shorten). Only valid if shorten=true.
  • fullview-on-click: True if full content should be opened in a new view on click. Only valid if shorten=true.

mmIframe

Iframes can be annoying in Mobile, especially in iOS. This directive is meant to make your life easier when dealing with them.

The non-relative links inside the iframe will be opened using an external browser. Relative links will be opened inside the iframe.

Please notice that all popups opened by the iframe will be blocked since MoodleMobile doesn't support popups.

mmImageViewer

It's usual to shrink some images in Mobile to make them fit the device's width and height. With this directive you can use an icon or link to see a certain image at full size with scrolling.

Please notice that mmFormatText automatically resizes images to fit the device screen and adds this directive to a magnifying glass icon.

mmLoading

In most cases the data to show in a view is retrieved from a Moodle site. These operations can take some time, so you should show a loading to show the user that the app is doing something.

This directive will replace your content with a loading spinner until your data is ready. You just need to wrap your HTML code inside this directive:

   <mm-loading hide-until="eventLoaded"> <ul class="list"> ... </ul> </mm-loading>

You need to use the hide-until attribute to tell the directive when is your data ready. In the example above, when the scope's eventLoaded variable is set to true then the spinner will disappear and the content will be shown.

You can configure the message to show while the spinner is shown (by default, "Loading") and also the spinner's padding top.

mmNavigationBar

This directive shows a navigation bar with 3 possible buttons: an arrow right, an arrow left and a "info" button.

You can see it in action in IMSCP and SCORM modules.

mmNoInputValidation

Sometimes we might want to disable automatic validation on some input fields (like URLs). This directive allows us to do so.

mmSplitView and mmSplitViewLink

It's usual in Mobile apps that in a tablet device you can see 2 views at once in two different panes, while in a smartphone you need to navigate through them 1 by 1. The split view directive allows us to achieve this.

The split view has two panes: the left one (parent or menu) and the right one (child). In tablet the user will be able to see both panels at the same time, but in a smartphone they will be seen as two different views, one at once. Each pane will have its own state, view and controller.

You should place the mm-split-view directive in the template of the left pane's state. You need to wrap all the HTML that should be in the left pane inside this directive:

   <mm-split-view component="mmaModForumDiscussionsList">
        <!-- CONTENT TO SHOW ON THE LEFT PANE (MENU) -->
    </mm-split-view>

It's important to supply a unique component to the directive to prevent collisions with other split views.

To change the right pane contents, the mmSplitViewLink directive is needed. If you want that the right pane contents change when the user clicks on some links in the left pane, then these links need to have the mm-split-view-link attribute with the state to load. Example:

   <mm-split-view component="mmaModForumDiscussionsList">
        <a mm-split-view-link="site.mod_forum-discussion({cid: courseid, discussionid: discussion.discussion})"></a>
    </mm-split-view>

In the example above, when the link is clicked the state 'site.mod_forum-discussion' will be loaded in the right pane.

The mmSplitView directive will try to load the first mmSplitViewLink when the view is loaded. You can change the link to load and when it should be loaded by using the attributes "load" and "loadWhen". If you don't have access to the directive's scope but you still want to configure it then you can use the mmCoreSplitViewLoad event. When the directive receives this event it will try to immediately load the link supplied (if no link is supplied then it will load the first link found). Example to load the second link:

   $rootScope.$broadcast(mmCoreSplitViewLoad, {load: 2});

Important considerations:

  • The directive mm-split-view-link needs to be placed inside a mm-split-view, otherwise it won't work. Both directives need to be in the left pane template, placing any of them in the right pane template can cause errors and unexpected behaviour.
  • Due to a limitation in Angular ui-router, the left pane state and the right pane state should NOT have parameters with the same name but different value, it can cause unexpected behaviors. Example: if the left pane loads a state with param 'courseid', then all the states that can be loaded in the right pane should avoid having a parameter named 'courseid'. The right pane state can have a 'courseid' param only if it will always have the same value than in left pane state. This is due to state hierarchy in ui-router.

URL scheme API

The Moodle Mobile app can be opened using links via the app's custom URL scheme:

Links are of the form moodlemobile://link=https://yourmoodlesite.org/mod/...

For example, the link moodlemobile://link=https://mysite.es/mod/choice/view.php?id=8 will open the mobile app and display a choice activity.

You can specify an user (if you can guess the current user username) moodlemobile://link=jsmith@https://mysite.es/mod/choice/view.php?id=8

If the user has the mobile app installed in his device and he is logged in the site or he added the site, the app will display the resource required. If the site wasn't added, the app will require the user credentials for that site.

Development

See Moodle Mobile Plugins Development.


Source code:


Creating your custom app

See Moodle Mobile Customization.


See also