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) Developing a plugin tutorial: Difference between revisions

From MoodleDocs
No edit summary
 
(30 intermediate revisions by 5 users not shown)
Line 1: Line 1:
{{Template:WillNotMigrate}}
{{Moodle Mobile 2 (Ionic 1)}}
IMPORTANT: This documentation will be obsolete in May 2018, please [https://moodle.org/mod/forum/discuss.php?d=360412 read this announcement].
== Introduction ==
== Introduction ==


This tutorial describes all the steps needed for developing a plugin for the MoodleMobile app.
This tutorial describes all the steps needed for developing an add-on for the Moodle Mobile app.


In this tutorial we make the following assumptions:
In this tutorial we make the following assumptions:


* You have a medium/good skills/understand of Javascript, PHP and Moodle architecture
* You have a medium/good skills/understand of AngularJS/Ionic, PHP and Moodle architecture
* You know what a Web Service is and how it works in Moodle
* You know what a Web Service is and how it works in Moodle
* The plugin you are going to develop requires a Web Service in your Moodle installation to work
* The plugin you are going to develop requires a Web Service in your Moodle installation to work


== Plugin spec ==
There is a second part of this tutorial where we extend the add-on features [[Moodle Mobile Developing a plugin tutorial part 2]]


We want to develop a "grades" plugin that will display the current user grades for all the gradable activities in a course the user is enroll in.
== Add-on spec ==


A screenshot of the desired result:
We want to develop a plugin that will display a new button in the user profile for adding new notes to the user. This plugin is suitable for course managers of teachers.


[[File:MoodleMobileTutorial01.png]]
A couple of screenshots with the desired result:


As you can see there is a new menu called "Grades" that displays a page in the middle panel showing all the course activities. When we click in an activity, the right panel shows the grade / maximum grade for the current user.
{|
 
| [[File:MoodleMobile2Tutorial01.png|thumb|Initial  view]]
In order to achieve this we will have to develop:
| [[File:MoodleMobile2Tutorial02.png|thumb|Course contents view]]
* A plugin for Moodle containing a new Web Service that returns grades and..
|}
* A MoodleMobile plugin


== Steps ==
== Steps ==
Line 27: Line 31:
=== Set up your development environment ===
=== Set up your development environment ===


Clone or fork the official git repository [https://github.com/moodlehq/moodlemobile] in a local directory.
Please, read [[Setting up your development environment for Moodle Mobile 2]]
 
Set up your development browser, see [[Moodle_Mobile]], I mainly use the Google Chrome browser.


=== Set up your Moodle installation ===
=== Set up your Moodle installation ===
Line 35: Line 37:
Enable debugging, disable caches, etc... the typical settings for developing.
Enable debugging, disable caches, etc... the typical settings for developing.


Enable the Web Services and also the protocols you are going to use for testing and developing (REST is mandatory). See [[Web Services]] documentation.
Enable the Mobile Service via Admin / Plugins / Web Services / Mobile
 
=== Develop the Moodle Web Services you are going to need ===
 
In this case we'll use an existing web service (core_notes_create_notes) that is already available in the Moodle Mobile app service, if you need custom functions you will need to develop a local plugin including there your new Web Services (see https://moodle.org/plugins/view/local_wstemplate) and create a custom service.
 
=== Develop the Moodle Mobile add-on ===
 
The full source code can be found here: https://github.com/moodlehq/moodlemobile2/tree/master/www/addons/notes
 
'''www/addons/notes/main.js'''
<syntaxhighlight lang="javascript">
angular.module('mm.addons.notes', [])
 
.run(function($mmUserDelegate, $mmaNotesHandlers) {
 
    $mmUserDelegate.registerPlugin('mmaNotes:addNote', $mmaNotesHandlers.addNote);
 
});
</syntaxhighlight>
 
Here we declare the plugin, and register the Plugin (this means that the plugin will be displayed in the User profile.
 
We will use the $mmaNotesHandlers service for holding all the code related to the plugin availability and main functions
 
There are more delegates for registering your plugin, see:
* www/addons/mod_forum/main.js (Module delegate, and module actions delegate)
* www/addons/messages/main.js (Main side menu delegate, and multiple delegates for the user profile)
 
'''www/addons/notes/services/handlers.js'''
<syntaxhighlight lang="javascript">
 
self.isEnabled = function() {
    return $mmaNotes.isPluginEnabled();
};
 
self.isEnabledForUser = function(user, courseId) {
  // Active course required.
  return courseId && user.id != $mmSite.getUserId();
};
</syntaxhighlight>
 
These are mandatory functions that will check our plugin availability, the first one will rely in the $mmaNotes service (isPluginEnabled function), the second ones checks that the user profile is being viewed inside a course (because the WS requires it) and that the current user is not the required user (so you don't see the buttons in your own profile).
 
<syntaxhighlight lang="javascript">


=== Develop the Moodle local/mod/block/... plugin ===
        self.getController = function(user, courseid) {


You can find the full source code here:
            return function($scope) {
https://github.com/cvaconsulting/moodle-local_custommm


In this case we've developed a local plugin that contains a Web Service that returns grades as described here: https://tracker.moodle.org/browse/MDL-30085
                // Button title.
                $scope.title = 'mma.notes.addnewnote';


Notice that my version has some differences with the previous mentioned one. I have fixed some small bugs and changed some parameters.
                $ionicModal.fromTemplateUrl('addons/notes/templates/add.html', {
                    scope: $scope,
                    animation: 'slide-in-up'
                }).then(function(m) {
                    $scope.modal = m;
                });


=== Test your Web Services using a demo client ===
                $scope.closeModal = function(){
                    $scope.modal.hide();
                };


Before continuing you must test that your Web Service works, the local plugin contains a demo REST client, see:
                $scope.addNote = function(){
                    // Freeze the add note button.
                    $scope.processing = true;


https://github.com/cvaconsulting/moodle-local_custommm/blob/master/client/client.php
                    $mmaNotes.addNote(user.id, courseid, $scope.note.publishstate, $scope.note.text).then(function() {
                        $translate('mma.notes.eventnotecreated').then(function(str) {
                            $ionicLoading.show({
                                template: str,
                                duration: 2000
                            });
                        });
                    }, function(error) {
                        $mmUtil.showErrorModal(error);
                    }).finally(function() {
                        $scope.closeModal();
                    });
                };


=== Create your custom Service ===
                $scope.action = function($event) {
                    $event.preventDefault();
                    $event.stopPropagation();


A service is a set of functions for an specific purpose. Since Moodle 2.1 there is a pre-built service that contains all the functions needed for the official mobile app.
                    $scope.note = {
                        publishstate: 'personal',
                        text: ''
                    };
                    $scope.processing = false;


This service cannot be edited for adding new functions so we don't have a simple way for make available our new Web Service function to the mobile app.
                    $scope.modal.show();
                };
            };
        };


The only way for fix this is creating a new Service in Moodle, and configuring our app for pointing to this new service.
</syntaxhighlight>


There are two ways of creating a Service in Moodle:
These is the main function code, it does the following:
* $scope.title = 'mma.notes.addnewnote'; This sets the button title, note that we reference to the addnewnote string that is in the www/addons/notes/lang/en.json file (we need to prefix the string id to identify the plugin)
* $ionicModal.fromTemplateUrl - Creates the modal we are going to use for displaying the form, the HTML code of the form is in www/addons/notes/templates/add.html
* $scope.closeModal - Helper function for closing the modal inside the modal via a button
* $scope.addNote - This function will be called when the user clicks the "Add a note" button in the modal, it will use the $mmaNotes service that handles all the logic WS logic for creating the note
* $scope.action - This function will be called when the user clicks the "Add a note" button in the user profile view, it will open the modal and set the default values


* Using a db/services.php in your plugin as mentioned here: [[External services description]]
'''www/addons/notes/services/notes.js'''
<syntaxhighlight lang="javascript">


* Creating the service in your Moodle installation using the forms in Admin > Plugins > Web Services > External Services
angular.module('mm.addons.notes')


In this case we've created the service using the Moodle forms because it gives more flexibility in some aspects, there is a big impediment, due to https://tracker.moodle.org/browse/MDL-29807 in order to add a shortname for the new Service, we have to edit manually the Moodle database for adding the shortname in the dbprefix_external_services table
.factory('$mmaNotes', function($mmSite, $log, $q) {
    $log = $log.getInstance('$mmaNotes');


[[File:MoodleMobileTutorial02.png]]
    var self = {};


When creating, the flats "Can download files" and "Enabled" must be checked.
    self.addNote = function(userId, courseId, publishState, noteText) {
        var data = {
            "notes[0][userid]" : userId,
            "notes[0][publishstate]": publishState,
            "notes[0][courseid]": courseId,
            "notes[0][text]": noteText,
            "notes[0][format]": 1
        };
        return $mmSite.write('core_notes_create_notes', data);
    };


=== Set up your MoodleMobile client ===
    self.isPluginEnabled = function() {
        var infos;


The most important change you have to do is edit the config.json file and change the wsservice parameter in order to point to the shortname of the Service you created before.
        if (!$mmSite.isLoggedIn()) {
            return false;
        } else if (!$mmSite.canUseAdvancedFeature('enablenotes')) {
            return false;
        } else if (!$mmSite.wsAvailable('core_notes_create_notes')) {
            return false;
        }


You have to add also the name of the plugin you are developing at the final of the plugins parameter:
        return true;
    };


"plugins" : ["notifications", "upload", "contents", "participants", "addcontact", "addnote", "sendmessage", "grades"],
    return self;
});
</syntaxhighlight>


Notice that, for avoid errors, this last change should be done once the file plugins/grades/main.js file exists
This file contains the notes service:
* isPluginEnabled - Checks if the notes functionality is available by checking Moodle settings and the required WS availability
* addNote - Will call the remote WS for adding the note via the $mmSite.write helper method (the $mmService has also a read method for retrieving data)


=== Develop the MoodleMobile plugin ===
'''/www/addons/notes/templates/add.html'''
<syntaxhighlight lang="html4strict">
<ion-modal-view class="product edit create">
    <form name="itemEdit" ng-submit="addNote()">
        <ion-header-bar class="bar-header">
            <h1 class="title">{{ 'mma.notes.addnewnote' | translate }}</h1>
            <a class="button" ng-click="closeModal()">{{ 'mm.core.cancel' | translate }}</a>
        </ion-header-bar>
        <ion-content has-bouncing="true">
            <div class="row">
                <div class="col">
                    <label class="item item-input item-select">
                        <div class="input-label">
                          {{ 'mma.notes.publishstate' | translate }}
                        </div>
                        <select ng-model="note.publishstate" name="publishstate">
                            <option value="personal">{{ 'mma.notes.personalnotes' | translate }}</option>
                            <option value="course">{{ 'mma.notes.coursenotes' | translate }}</option>
                            <option value="site">{{ 'mma.notes.sitenotes' | translate }}</option>
                        </select>
                    </label>
                </div>
            </div>
            <div class="row">
                <div class="col">
                    <label class="item item-input text">
                        <textarea placeholder="{{ 'mma.notes.note' | translate }}" rows="5" ng-model="note.text" name="text" lose-focus-on-return required="required"></textarea>
                    </label>
                </div>
            </div>
            <div class="padding">
                <button type="submit" class="button button-block" ng-disabled="processing || !note.text.length || note.text.length < 2">
                    {{ 'mma.notes.addnewnote' | translate }}
                </button>
            </div>
        </ion-content>
    </form>
</ion-modal-view>
</syntaxhighlight>


The full source code can be found here:
This file includes the code for the modal. As you can see the close button will call the closeModal() function meanwhile the Add a note button will submit the form that will invoke the addNote function


https://github.com/cvaconsulting/moodlemobile-grades
For more information, refer to http://ionicframework.com/docs/api/service/$ionicModal/


=== Deliver your plugin ===
== See also ==


Android: Package your plugin using [https://build.phonegap.com/apps PhoneGap build]
[[Moodle Mobile Developing a plugin tutorial part 2]]


iOs: Further instructions soon
[[Category: Tutorial]]

Latest revision as of 07:14, 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.


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

Introduction

This tutorial describes all the steps needed for developing an add-on for the Moodle Mobile app.

In this tutorial we make the following assumptions:

  • You have a medium/good skills/understand of AngularJS/Ionic, PHP and Moodle architecture
  • You know what a Web Service is and how it works in Moodle
  • The plugin you are going to develop requires a Web Service in your Moodle installation to work

There is a second part of this tutorial where we extend the add-on features Moodle Mobile Developing a plugin tutorial part 2

Add-on spec

We want to develop a plugin that will display a new button in the user profile for adding new notes to the user. This plugin is suitable for course managers of teachers.

A couple of screenshots with the desired result:

Initial view
Course contents view

Steps

Set up your development environment

Please, read Setting up your development environment for Moodle Mobile 2

Set up your Moodle installation

Enable debugging, disable caches, etc... the typical settings for developing.

Enable the Mobile Service via Admin / Plugins / Web Services / Mobile

Develop the Moodle Web Services you are going to need

In this case we'll use an existing web service (core_notes_create_notes) that is already available in the Moodle Mobile app service, if you need custom functions you will need to develop a local plugin including there your new Web Services (see https://moodle.org/plugins/view/local_wstemplate) and create a custom service.

Develop the Moodle Mobile add-on

The full source code can be found here: https://github.com/moodlehq/moodlemobile2/tree/master/www/addons/notes

www/addons/notes/main.js

angular.module('mm.addons.notes', [])

.run(function($mmUserDelegate, $mmaNotesHandlers) {

    $mmUserDelegate.registerPlugin('mmaNotes:addNote', $mmaNotesHandlers.addNote);

});

Here we declare the plugin, and register the Plugin (this means that the plugin will be displayed in the User profile.

We will use the $mmaNotesHandlers service for holding all the code related to the plugin availability and main functions

There are more delegates for registering your plugin, see:

  • www/addons/mod_forum/main.js (Module delegate, and module actions delegate)
  • www/addons/messages/main.js (Main side menu delegate, and multiple delegates for the user profile)

www/addons/notes/services/handlers.js

self.isEnabled = function() {
    return $mmaNotes.isPluginEnabled();
};

self.isEnabledForUser = function(user, courseId) {
   // Active course required.
   return courseId && user.id != $mmSite.getUserId();
};

These are mandatory functions that will check our plugin availability, the first one will rely in the $mmaNotes service (isPluginEnabled function), the second ones checks that the user profile is being viewed inside a course (because the WS requires it) and that the current user is not the required user (so you don't see the buttons in your own profile).

        self.getController = function(user, courseid) {

            return function($scope) {

                // Button title.
                $scope.title = 'mma.notes.addnewnote';

                $ionicModal.fromTemplateUrl('addons/notes/templates/add.html', {
                    scope: $scope,
                    animation: 'slide-in-up'
                }).then(function(m) {
                    $scope.modal = m;
                });

                $scope.closeModal = function(){
                    $scope.modal.hide();
                };

                $scope.addNote = function(){
                    // Freeze the add note button.
                    $scope.processing = true;

                    $mmaNotes.addNote(user.id, courseid, $scope.note.publishstate, $scope.note.text).then(function() {
                        $translate('mma.notes.eventnotecreated').then(function(str) {
                            $ionicLoading.show({
                                template: str,
                                duration: 2000
                            });
                        });
                    }, function(error) {
                        $mmUtil.showErrorModal(error);
                    }).finally(function() {
                        $scope.closeModal();
                    });
                };

                $scope.action = function($event) {
                    $event.preventDefault();
                    $event.stopPropagation();

                    $scope.note = {
                        publishstate: 'personal',
                        text: ''
                    };
                    $scope.processing = false;

                    $scope.modal.show();
                };
            };
        };

These is the main function code, it does the following:

  • $scope.title = 'mma.notes.addnewnote'; This sets the button title, note that we reference to the addnewnote string that is in the www/addons/notes/lang/en.json file (we need to prefix the string id to identify the plugin)
  • $ionicModal.fromTemplateUrl - Creates the modal we are going to use for displaying the form, the HTML code of the form is in www/addons/notes/templates/add.html
  • $scope.closeModal - Helper function for closing the modal inside the modal via a button
  • $scope.addNote - This function will be called when the user clicks the "Add a note" button in the modal, it will use the $mmaNotes service that handles all the logic WS logic for creating the note
  • $scope.action - This function will be called when the user clicks the "Add a note" button in the user profile view, it will open the modal and set the default values

www/addons/notes/services/notes.js

angular.module('mm.addons.notes')

.factory('$mmaNotes', function($mmSite, $log, $q) {
    $log = $log.getInstance('$mmaNotes');

    var self = {};

    self.addNote = function(userId, courseId, publishState, noteText) {
        var data = {
            "notes[0][userid]" : userId,
            "notes[0][publishstate]": publishState,
            "notes[0][courseid]": courseId,
            "notes[0][text]": noteText,
            "notes[0][format]": 1
        };
        return $mmSite.write('core_notes_create_notes', data);
    };

    self.isPluginEnabled = function() {
        var infos;

        if (!$mmSite.isLoggedIn()) {
            return false;
        } else if (!$mmSite.canUseAdvancedFeature('enablenotes')) {
            return false;
        } else if (!$mmSite.wsAvailable('core_notes_create_notes')) {
            return false;
        }

        return true;
    };

    return self;
});

This file contains the notes service:

  • isPluginEnabled - Checks if the notes functionality is available by checking Moodle settings and the required WS availability
  • addNote - Will call the remote WS for adding the note via the $mmSite.write helper method (the $mmService has also a read method for retrieving data)

/www/addons/notes/templates/add.html

<ion-modal-view class="product edit create">
    <form name="itemEdit" ng-submit="addNote()">
        <ion-header-bar class="bar-header">
            <h1 class="title">{{ 'mma.notes.addnewnote' | translate }}</h1>
            <a class="button" ng-click="closeModal()">{{ 'mm.core.cancel' | translate }}</a>
        </ion-header-bar>
        <ion-content has-bouncing="true">
            <div class="row">
                <div class="col">
                    <label class="item item-input item-select">
                        <div class="input-label">
                          {{ 'mma.notes.publishstate' | translate }}
                        </div>
                        <select ng-model="note.publishstate" name="publishstate">
                            <option value="personal">{{ 'mma.notes.personalnotes' | translate }}</option>
                            <option value="course">{{ 'mma.notes.coursenotes' | translate }}</option>
                            <option value="site">{{ 'mma.notes.sitenotes' | translate }}</option>
                        </select>
                    </label>
                </div>
            </div>
            <div class="row">
                <div class="col">
                    <label class="item item-input text">
                        <textarea placeholder="{{ 'mma.notes.note' | translate }}" rows="5" ng-model="note.text" name="text" lose-focus-on-return required="required"></textarea>
                    </label>
                </div>
            </div>
            <div class="padding">
                <button type="submit" class="button button-block" ng-disabled="processing || !note.text.length || note.text.length < 2">
                    {{ 'mma.notes.addnewnote' | translate }}
                </button>
            </div>
        </ion-content>
    </form>
</ion-modal-view>

This file includes the code for the modal. As you can see the close button will call the closeModal() function meanwhile the Add a note button will submit the form that will invoke the addNote function

For more information, refer to http://ionicframework.com/docs/api/service/$ionicModal/

See also

Moodle Mobile Developing a plugin tutorial part 2