Difference between revisions of "Creating mobile question types"

Jump to: navigation, search
 
(21 intermediate revisions by the same user not shown)
Line 4: Line 4:
 
https://github.com/marcusgreen/moodle-qtype_TEMPLATE
 
https://github.com/marcusgreen/moodle-qtype_TEMPLATE
  
and it incorporates the skeleton code for supporting the App.
+
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
Line 40: Line 44:
  
 
==== html template ====
 
==== html template ====
Your question type will need an html/ionic markup template file that controls how it is displayed at runtime. This is the template from the core shortanswer question type and gives an idea of some of the options. You can see a real world examples of this file at  
+
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
 
https://github.com/Beedell/moodle-qtype_oumultiresponse/blob/wip1/mobile/oumr.html
 
and
 
and
 +
 
https://github.com/marcusgreen/moodle-qtype_gapfill/blob/master/mobile/addon-qtype-gapfill.html
 
https://github.com/marcusgreen/moodle-qtype_gapfill/blob/master/mobile/addon-qtype-gapfill.html
 +
 
(oumr.html is probably a more sensible naming convetion)
 
(oumr.html is probably a more sensible naming convetion)
  
 
Create a file named mobile/addon-qtype-YOURQTYPENAME.html
 
Create a file named mobile/addon-qtype-YOURQTYPENAME.html
  
<code>
+
<code xml>
<section ion-list *ngIf="question.text || question.text === ''">
+
<section class="list qtype-YOURQTYPENAME-container qtype-YOURQTYPENAME" ion-list *ngIf="question.text || question.text === ''">
     <ion-item text-wrap>
+
     <ion-item text-wrap class="addon-qtype-YOURQTYPENAME-container qtext">
         <p><core-format-text [component]="component" [componentId]="componentId" [text]="question.text"></core-format-text></p>
+
         <p><core-format-text [component]="component" [componentId]="componentId"  
 +
            [text]="question.text" (afterRender)="questionRendered()"></core-format-text></p>    
 
     </ion-item>
 
     </ion-item>
    <ion-input padding-left type="text" placeholder="{{ 'core.question.answer' | translate }}"
 
    [attr.name]="question.input.name" [value]="question.input.value" autocorrect="off"
 
    [disabled]="question.input.readOnly" [ngClass]="[question.input.correctClass]">
 
    </ion-input>
 
 
</section>
 
</section>
 
</code>
 
</code>
 +
 +
The reference to questionRendered() will call the method of that name in the mobile/mobile.js file.
  
 
==== classes/output/mobile.php ====
 
==== 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
 
This file connects PHP with the html/ionic markup template and the mobile.js javascript which contains most of the runtime logic
<code>
+
<code php>
 
namespace qtype_YOURQTYPENAME\output;
 
namespace qtype_YOURQTYPENAME\output;
  
Line 85: Line 95:
 
    
 
    
 
</Code>
 
</Code>
 +
 
==== mobile/mobile.js ====
 
==== mobile/mobile.js ====
<code>
+
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 that = this;
 
var result = {
 
var result = {
 
 
     componentInit: function() {
 
     componentInit: function() {
        /**
 
        * If the question is in a readonly state, e.g. after being
 
        * answered or in the review page then stop any further
 
        * selections.
 
        *
 
        * @param {NodeList} draggables
 
        * @param {MouseEvent} event
 
        * @return {string} value of target
 
        **/
 
 
         if (!this.question) {
 
         if (!this.question) {
 
             console.warn('Aborting because of no question received.');
 
             console.warn('Aborting because of no question received.');
Line 110: Line 118:
  
 
         // Replace Moodle's correct/incorrect and feedback classes with our own.
 
         // 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.replaceCorrectnessClasses(div);
 
         this.CoreQuestionHelperProvider.replaceFeedbackClasses(div);
 
         this.CoreQuestionHelperProvider.replaceFeedbackClasses(div);
Line 124: Line 133:
 
             this.question.feedbackHTML = true;
 
             this.question.feedbackHTML = true;
 
         }
 
         }
 +
       
 +
        this.question.text = this.CoreDomUtilsProvider.getContentsOfElement(div, '.qtext');
  
 
         if (typeof this.question.text == 'undefined') {
 
         if (typeof this.question.text == 'undefined') {
 
             this.logger.warn('Aborting because of an error parsing question.', this.question.name);
 
             this.logger.warn('Aborting because of an error parsing question.', this.question.name);
 
             return this.CoreQuestionHelperProvider.showComponentError(this.onAbort);
 
             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.
 
         // Wait for the DOM to be rendered.
 
         setTimeout(() => {
 
         setTimeout(() => {
+
            //put stuff here that will be pulled from the rendered question
 
         });
 
         });
 
         return true;
 
         return true;
Line 138: Line 155:
 
};
 
};
 
result;
 
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;
}