Note:

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

Question behaviours: Difference between revisions

From MoodleDocs
No edit summary
No edit summary
Line 2: Line 2:
This page explains how to go about writing a new Question Interaction Model for the new Moodle [[Question Engine 2|question engine]].
This page explains how to go about writing a new Question Interaction Model for the new Moodle [[Question Engine 2|question engine]].


Previous section: [[Question Engine 2:Design|Design]]
Previous section: [[Overview_of_the_Moodle_question_engine|Overview_of_the_Moodle_question_engine]]


{{Work in progress}}
{{Work in progress}}
Line 10: Line 10:
Also note that all the question engine code has extensive PHP documenter comments that should explain the purpose of every class and method. This document is supposed to provide an overview of the key points to get you started. It does not attempt to duplicate all the details in the PHPdocs.
Also note that all the question engine code has extensive PHP documenter comments that should explain the purpose of every class and method. This document is supposed to provide an overview of the key points to get you started. It does not attempt to duplicate all the details in the PHPdocs.


This document assumes that you understand the data model, where a question attempt comprises a number of steps, and each step has some properties like a state and a mark, and an array of submitted data.
This document assumes that you understand the data model, where a question attempt comprises a number of steps, and each step has some properties like a state and a mark, and an array of submitted data. This is explained in the [[Overview_of_the_Moodle_question_engine|overview_of_the_Moodle_question_engine]].





Revision as of 19:27, 26 February 2010

This page explains how to go about writing a new Question Interaction Model for the new Moodle question engine.

Previous section: Overview_of_the_Moodle_question_engine

Note: This page is a work-in-progress. Feedback and suggested improvements are welcome. Please join the discussion on moodle.org or use the page comments.


To learn to write Question Interaction Models, you are highly encouraged to read the code of some of the existing interaction models in the Moodle code base. The code for most interaction models is shorter than these instructions!

Also note that all the question engine code has extensive PHP documenter comments that should explain the purpose of every class and method. This document is supposed to provide an overview of the key points to get you started. It does not attempt to duplicate all the details in the PHPdocs.

This document assumes that you understand the data model, where a question attempt comprises a number of steps, and each step has some properties like a state and a mark, and an array of submitted data. This is explained in the overview_of_the_Moodle_question_engine.


File layout

A question interaction model plugin lives in a folder in question/interaction. The layout inside that folder follows the typical layout for a Moodle plugin. For example, inside question/interaction/mymodel/ we would have:

model.php
This contains the definition of the qim_mymodel class, which should extend the question_interaction_model base class.
renderer.php
This contains the definition of the qim_mymodel_renderer class, which should extend the qim_renderer base class.
simpletest/...
Contains the unit tests for this interaction model. You are strongly encouraged to write thorough unit tests to ensure the correctness of your interaction model.
lang/en_utf8/qim_mymodel.php
English language strings for this interaction model. You can, of course, include other languages too. The language file must define at least the string giving the model a name, for example $string['mymodel'] = 'My model';.

Note that Question Interaction Models are not expected to have their own database tables or capabilities. Therefore, there should not be a db sub-folder.

In future, we will probably add the ability for an Interaction Model to have a settings.php file, so that it can have configuration options.


Question model class

One instance of this class will be created for each question_attempt that uses this model.

This documentation just lists the metods that you are likely to need to override. See the PHPdocumentor comments for a complete API documentation.


Class declaration

The class name for the model class must be the plugin name (e.g. mymodel) prefixed by qim_. You must extend the question_interaction_model base class, or another model class.

class qim_mymodel extends question_interaction_model {

   // ...

}

You should not need to override the constructor.


Fields

When an interaction model class is created, the fields qa and question are initialised to point to the question attempt and question that this model is being used by.

From the base class code:

   // From the base class.
   /** @var question_attempt */
   protected $qa;
   /** @var question_definition */
   protected $question;
   public function __construct(question_attempt $qa) {
       $this->qa = $qa;
       $this->question = $qa->get_question();
   }


IS_ARCHETYPAL

Some interaction models define models that should be offered as options in the user interface. For example CBM, interactive mode, and so on. Other models are for internal use. For example qim_informationitem, qim_interactiveadaptedformyqtype. This is indicated by defining a constant IS_ARCHETYPAL in the class. True means that this model will be offered as an option in the UI.

   const IS_ARCHETYPAL = true; // or false


required_question_definition_class

Most interaction models can only work with a particular subclasses of question_definition. For example perhpas they can only work with questions that are question_graded_automaticallys. This method lets the interaction model document that. The type of question passed to the constructor is then checked against the class name returned by this function.

Example from qim_deferredfeedback:

   public function required_question_definition_class() {
       return 'question_graded_automatically';
   }


get_min_fraction

Returns the smallest possible fraction that this model, applied to this question, may give. Normally this will be the min fraction or the question type, however some models (for example CBM) manipulate the question scores, and so need to return an adjusted min_fraction.

For example, from qim_deferredfeedback:

   public function get_min_fraction() {
       return $this->question->get_min_fraction();
   }

From qim_qim_deferredcbm:

   public function get_min_fraction() {
       return question_cbm::adjust_fraction(
               parent::get_min_fraction(), question_cbm::HIGH);
   }


get_expected_data

Some interaction models display additional form controls. When the question from is submitted, these submitted values are read using the standard optional_param function. The question engine needs to know what parameters to ask for, and with what types.

The get_expected_data function should return an array of expected parameters, with the corresponding param type. Note that the array of expected parameters may depend on the current state of the question attempt. For example, from qim_deferredcbm

   public function get_expected_data() {
       if (question_state::is_active($this->qa->get_state())) {
           return array('certainty' => PARAM_INT);
       }
       return array();
   }


init_first_step

You are unlikely to need to override this method. This is called when the question attempt is actually stared. It gives the model a chance to do any necessary initialisation, including passing on the request to the question type. This is, for example, how the choices in a multiple choices question are shuffled.

From the base class:

   public function init_first_step(question_attempt_step $step) {
       $this->question->init_first_step($step);
   }


process_action

This is the most important method. It controls what happens when someone does something with the question (other than starting it, which is handled by init_first_step.)

When the method is called, a pending attempt step is passed in. This method can either return false, to indicate nothing interesting happened, and the pending step should be discarded. Or, it can update that step, and return true, to have the new step retained.

Typically, the method is implemented by examining the current state of the attempt, and the pending step, to decide what sort of action this is, and then delegating to a more specific method.

For example, from qim_deferredfeedback

   public function process_action(question_attempt_step $pendingstep) {
       if ($pendingstep->has_im_var('comment')) {
           return $this->process_comment($pendingstep);
       } else if ($pendingstep->has_im_var('finish')) {
           return $this->process_finish($pendingstep);
       } else {
           return $this->process_save($pendingstep);
       }
   }

Note that there are some special actions that every model must be able to handle:

im_var comment => 'A manual comment'
This may also have im_var mark and im_var maxmark, if the question is graded. This is the action that is fired when a user manually grades a question.
im_var finish => 1
This is the action that is generated when the user submits and finishes a whole usage. For example when they click 'Submit all and finish' in a quiz.

The base class implements the process_comment method in a way that should be suitable for most models. It also provides a process_save implementation that may be suitable for most models.


adjust_display_options

This method is called before a question is rendered, so that the interaction model can change the display options depending on the current state of the question. So, for example, after the question is finished, it should be displayed in read-only mode. Before the student had submitted an answer, no feedback should be displayed.

For example, the base class does

   public function adjust_display_options(question_display_options $options) {
       if (question_state::is_finished($this->qa->get_state())) {
           $options->readonly = true;
       } else {
           $options->hide_all_feedback();
       }
   }


Renderer class

Renderers are all about generating HTML output. The overall output of questions is controlled by the core_question_renderer::question(...), which in turn calls other methods of itself, the question type renderer and the interaction model renderer, to generate the various bits of output. See this overview of the parts of a question.

Once again, in what follows, I only list the methods your are most likely to want to override.


controls

This is a block of output in the question formulation area. It goes after all the question type output in this area.

Two examples. This method is used by the interactive model to show the Submit button (if the question is in an appropriate state). It is used by the CBM model to show the certainty choices.


feedback

This is a block of output in the feedback area of the question. It is used, for example in interactive mode, to show the Try again button.


Unit tests

For a general introduction, see the Moodle unit testing documentation.

Most of the interaction models are currently tested by working one or more test questions through a sequence of example inputs. To do this they subclass qim_walkthrough_test_base which provides a lot of helper methods to facilitate writing tests like that.

The best way to start is probably to look at the tests for some of the standard interaction models.


See also

In the next section, Developing a Question Type I describe what a developer will need to do to create a Question Type plugin for the new system.

  • The PHP documenter comments that explain the purposes of every method in the question engine code.
  • Back to Question Engine 2