Difference between revisions of "Creating mobile question types"

Jump to: navigation, search
(Created page with " An empty template for creating quiz question types is available from here and it incorporates the skeleton code for supporting the App. https://github.com/marcusgreen/moodle...")
 
 
(33 intermediate revisions by the same user not shown)
Line 1: Line 1:
  
An empty template for creating quiz question types is available from here and it incorporates the skeleton code
+
An empty template for creating quiz question types with mobile support is available from here
for supporting the App.
 
  
 
https://github.com/marcusgreen/moodle-qtype_TEMPLATE
 
https://github.com/marcusgreen/moodle-qtype_TEMPLATE
 +
 +
There was a presentation on modifying questions type to support the app at the November 2018 developers meeting. You can find a recording of this here
 +
 +
https://moodle.zoom.us/recording/play/mtX7qcu_G9HnEIxfEuxjMVCX7PmZqAG7128XnAWUkLfvVLusBS07tIyJ5U-pv1Sm?continueMode=true
 +
 +
The associated slide show is here. https://docs.google.com/presentation/d/1jUyfULul_g6hh_3s-wISXIVphv_RKqgpHuxMHZ0F800/edit#slide=id.p
  
 
The files that need to be modified/added are
 
The files that need to be modified/added are
  
db/mobile.php
+
==== db/mobile.php ====
classes/output/mobile.php
 
mobile/mobile.js
 
mobile/addon-qtype-YOURQTYPENAME.html
 
styles_app.css
 
 
 
Create a new file db/mobile.php
 
 
<code php>
 
<code php>
 
defined('MOODLE_INTERNAL') || die();
 
defined('MOODLE_INTERNAL') || die();
Line 42: Line 41:
 
     ]
 
     ]
 
];
 
];
 +
</code>
 +
 +
==== html template ====
 +
Your question type will need an html/ionic markup template file that controls how it is displayed at runtime.
 +
Ionic markup is based on the Angular template syntax which you can read about here
 +
https://angular.io/guide/template-syntax
 +
 +
You can see a real world examples of question type templates at
 +
 +
https://github.com/Beedell/moodle-qtype_oumultiresponse/blob/wip1/mobile/oumr.html
 +
and
 +
 +
https://github.com/marcusgreen/moodle-qtype_gapfill/blob/master/mobile/addon-qtype-gapfill.html
 +
 +
(oumr.html is probably a more sensible naming convetion)
 +
 +
Create a file named mobile/addon-qtype-YOURQTYPENAME.html
 +
 +
<code xml>
 +
<section class="list qtype-YOURQTYPENAME-container qtype-YOURQTYPENAME" ion-list *ngIf="question.text || question.text === ''">
 +
    <ion-item text-wrap class="addon-qtype-YOURQTYPENAME-container qtext">
 +
        <p><core-format-text  [component]="component" [componentId]="componentId"
 +
            [text]="question.text" (afterRender)="questionRendered()"></core-format-text></p>   
 +
    </ion-item>
 +
</section>
 +
</code>
 +
 +
The reference to questionRendered() will call the method of that name in the mobile/mobile.js file.
 +
 +
==== classes/output/mobile.php ====
 +
This file connects PHP with the html/ionic markup template and the mobile.js javascript which contains most of the runtime logic
 +
<code php>
 +
namespace qtype_YOURQTYPENAME\output;
 +
 +
defined('MOODLE_INTERNAL') || die();
 +
 +
class mobile {
 +
 +
class mobile {
 +
    public static function mobile_get_YOURQTYPENAME() {
 +
        global $CFG;
 +
        return [
 +
            'templates' => [
 +
                [
 +
                    'id' => 'main',
 +
                    'html' => file_get_contents($CFG->dirroot .'/question/type/YOURQTYPENAME/mobile/addon-qtype-YOURQTYPENAME.html')
 +
                    ]
 +
            ],
 +
            'javascript' => file_get_contents($CFG->dirroot . '/question/type/YOURQTYPENAME/mobile/mobile.js')
 +
        ];
 +
    }
 +
}
 +
 
 +
</Code>
 +
 +
==== mobile/mobile.js ====
 +
This is where most of the logic is placed such as changing css classes.
 +
You can see real world implementations of this file at
 +
https://github.com/Beedell/moodle-qtype_oumultiresponse/blob/wip1/mobile/oumr.js
 +
 +
and
 +
 +
https://github.com/marcusgreen/moodle-qtype_gapfill/blob/master/mobile/mobile.js
 +
<code js>
 +
var that = this;
 +
var result = {
 +
    componentInit: function() {
 +
        if (!this.question) {
 +
            console.warn('Aborting because of no question received.');
 +
            return that.CoreQuestionHelperProvider.showComponentError(that.onAbort);
 +
        }
 +
        const div = document.createElement('div');
 +
        div.innerHTML = this.question.html;
 +
        // Get question questiontext.
 +
        const questiontext = div.querySelector('.qtext');
 +
 +
        // Replace Moodle's correct/incorrect and feedback classes with our own.
 +
        // Only do this if you want to use the standard classes
 +
        this.CoreQuestionHelperProvider.replaceCorrectnessClasses(div);
 +
        this.CoreQuestionHelperProvider.replaceFeedbackClasses(div);
 +
 +
        // Treat the correct/incorrect icons.
 +
        this.CoreQuestionHelperProvider.treatCorrectnessIcons(div);
 +
 +
 +
        if (div.querySelector('.readonly') !== null) {
 +
            this.question.readonly = true;
 +
        }
 +
        if (div.querySelector('.feedback') !== null) {
 +
            this.question.feedback = div.querySelector('.feedback');
 +
            this.question.feedbackHTML = true;
 +
        }
 +
       
 +
        this.question.text = this.CoreDomUtilsProvider.getContentsOfElement(div, '.qtext');
 +
 +
        if (typeof this.question.text == 'undefined') {
 +
            this.logger.warn('Aborting because of an error parsing question.', this.question.name);
 +
            return this.CoreQuestionHelperProvider.showComponentError(this.onAbort);
 +
        }
 +
 +
        // Called by the reference in *.html to
 +
        // (afterRender)="questionRendered()
 +
        this.questionRendered = function questionRendered() {
 +
            //do stuff that needs the question rendered before it can run.
 +
        }
 +
 +
        // Wait for the DOM to be rendered.
 +
        setTimeout(() => {
 +
            //put stuff here that will be pulled from the rendered question
 +
        });
 +
        return true;
 +
    }
 +
};
 +
result;
 +
</code>
 +
 +
==== mobile/styles_app.css ====
 +
This assumes you have class="qtype-YOURQTYPENAME-container in your html template. Changes will be updated when you increment the value
 +
of styles[version] in  db/mobile.php
 +
<code css>
 +
.qtype-YOURQTYPENAME .yourclassname {
 +
    attributename: attributevalue;
 +
}
 
</code>
 
</code>

Latest revision as of 10:54, 11 January 2019

An empty template for creating quiz question types with mobile support is available from here

https://github.com/marcusgreen/moodle-qtype_TEMPLATE

There was a presentation on modifying questions type to support the app at the November 2018 developers meeting. You can find a recording of this here

https://moodle.zoom.us/recording/play/mtX7qcu_G9HnEIxfEuxjMVCX7PmZqAG7128XnAWUkLfvVLusBS07tIyJ5U-pv1Sm?continueMode=true

The associated slide show is here. https://docs.google.com/presentation/d/1jUyfULul_g6hh_3s-wISXIVphv_RKqgpHuxMHZ0F800/edit#slide=id.p

The files that need to be modified/added are

db/mobile.php

defined('MOODLE_INTERNAL') || die();
 
$addons = [
    "qtype_YOURQTYPENAME" => [
        "handlers" => [ // Different places where the add-on will display content.
            'YOURQTYPENAME' => [ // Handler unique name (can be anything).
                'displaydata' => [
                    'title' => 'YOURQTYPENAME question',
                    'icon' => '/question/type/YOURQTYPENAME/pix/icon.gif',
                    'class' => '',
                ],
                'delegate' => 'CoreQuestionDelegate', // Delegate (where to display the link to the add-on).
                'method' => 'mobile_get_YOURQTYPENAME',
                'offlinefunctions' => [
                    'mobile_get_YOURQTYPENAME' => [],// function in classes/output/mobile.php
                ], // Function needs caching for offline.
                'styles' => [
                    'url' => '/question/type/YOURQTYPENAME/mobile/styles_app.css',
                    'version' => '1.00'
                ]
            ]
        ],
        'lang' => [
                    ['pluginname', 'qtype_YOURQTYPENAME'], // matching value in  lang/en/qtype_YOURQTYPENAME
        ],
    ]
];

html template

Your question type will need an html/ionic markup template file that controls how it is displayed at runtime. Ionic markup is based on the Angular template syntax which you can read about here https://angular.io/guide/template-syntax

You can see a real world examples of question type templates at

https://github.com/Beedell/moodle-qtype_oumultiresponse/blob/wip1/mobile/oumr.html and

https://github.com/marcusgreen/moodle-qtype_gapfill/blob/master/mobile/addon-qtype-gapfill.html

(oumr.html is probably a more sensible naming convetion)

Create a file named mobile/addon-qtype-YOURQTYPENAME.html

<section class="list qtype-YOURQTYPENAME-container qtype-YOURQTYPENAME" ion-list *ngIf="question.text || question.text === ''">
    <ion-item text-wrap class="addon-qtype-YOURQTYPENAME-container qtext">
        <p><core-format-text  [component]="component" [componentId]="componentId" 
            [text]="question.text" (afterRender)="questionRendered()"></core-format-text></p>     
    </ion-item>
</section>

The reference to questionRendered() will call the method of that name in the mobile/mobile.js file.

classes/output/mobile.php

This file connects PHP with the html/ionic markup template and the mobile.js javascript which contains most of the runtime logic

namespace qtype_YOURQTYPENAME\output;
 
defined('MOODLE_INTERNAL') || die();
 
class mobile {
 
class mobile {
    public static function mobile_get_YOURQTYPENAME() {
        global $CFG;
        return [
            'templates' => [
                [
                    'id' => 'main',
                    'html' => file_get_contents($CFG->dirroot .'/question/type/YOURQTYPENAME/mobile/addon-qtype-YOURQTYPENAME.html')
                    ]
            ],
            'javascript' => file_get_contents($CFG->dirroot . '/question/type/YOURQTYPENAME/mobile/mobile.js')
        ];
    }
}

mobile/mobile.js

This is where most of the logic is placed such as changing css classes. You can see real world implementations of this file at https://github.com/Beedell/moodle-qtype_oumultiresponse/blob/wip1/mobile/oumr.js

and

https://github.com/marcusgreen/moodle-qtype_gapfill/blob/master/mobile/mobile.js

var that = this;
var result = {
    componentInit: function() {
        if (!this.question) {
            console.warn('Aborting because of no question received.');
            return that.CoreQuestionHelperProvider.showComponentError(that.onAbort);
        }
        const div = document.createElement('div');
        div.innerHTML = this.question.html;
         // Get question questiontext.
        const questiontext = div.querySelector('.qtext');
 
        // Replace Moodle's correct/incorrect and feedback classes with our own.
        // Only do this if you want to use the standard classes
        this.CoreQuestionHelperProvider.replaceCorrectnessClasses(div);
        this.CoreQuestionHelperProvider.replaceFeedbackClasses(div);
 
         // Treat the correct/incorrect icons.
        this.CoreQuestionHelperProvider.treatCorrectnessIcons(div);
 
 
        if (div.querySelector('.readonly') !== null) {
            this.question.readonly = true;
        }
        if (div.querySelector('.feedback') !== null) {
            this.question.feedback = div.querySelector('.feedback');
            this.question.feedbackHTML = true;
        }
 
         this.question.text = this.CoreDomUtilsProvider.getContentsOfElement(div, '.qtext');
 
        if (typeof this.question.text == 'undefined') {
            this.logger.warn('Aborting because of an error parsing question.', this.question.name);
            return this.CoreQuestionHelperProvider.showComponentError(this.onAbort);
        }
 
        // Called by the reference in *.html to 
        // (afterRender)="questionRendered()
        this.questionRendered = function questionRendered() {
            //do stuff that needs the question rendered before it can run.
        }
 
        // Wait for the DOM to be rendered.
        setTimeout(() => {
            //put stuff here that will be pulled from the rendered question
        });
        return true;
    }
};
result;

mobile/styles_app.css

This assumes you have class="qtype-YOURQTYPENAME-container in your html template. Changes will be updated when you increment the value of styles[version] in db/mobile.php

.qtype-YOURQTYPENAME .yourclassname {
     attributename: attributevalue;
}