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
Line 1: Line 1:
{{Template:Question_engine_2}}
{{Template:Question_engine_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 Behaviour for the new Moodle [[Question Engine 2|question engine]].


Previous section: [[Overview_of_the_Moodle_question_engine|Overview_of_the_Moodle_question_engine]]
Previous section: [[Overview_of_the_Moodle_question_engine|Overview_of_the_Moodle_question_engine]]
Line 6: Line 6:
{{Work in progress}}
{{Work in progress}}


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!
To learn to write Question Behaviours, you are highly encouraged to read the code of some of the existing behaviours in the Moodle code base. The code for most behaviours 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.
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.
Line 15: Line 15:
==File layout==
==File layout==


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


; model.php : This contains the definition of the <tt>qim_mymodel</tt> class, which should extend the <tt>question_interaction_model</tt> base class.
; behaviour.php : This contains the definition of the <tt>qim_mymodel</tt> class, which should extend the <tt>question_interaction_model</tt> base class.
; renderer.php : This contains the definition of the <tt>qim_mymodel_renderer</tt> class, which should extend the <tt>qim_renderer</tt> base class.
; renderer.php : This contains the definition of the <tt>qim_mymodel_renderer</tt> class, which should extend the <tt>qim_renderer</tt> 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.
; 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 <tt>$string['mymodel'] = 'My model';</tt>.
; lang/en_utf8/qbehaviour_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 <tt>$string['mymodel'] = 'My model';</tt>.


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.
Note that Question Behaviours 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.
In future, we will probably add the ability for a behaviour to have a settings.php file, so that it can have configuration options.




==Question model class==
==Question behaviour class==


One instance of this class will be created for each <tt>question_attempt</tt> that uses this model.
One instance of this class will be created for each <tt>question_attempt</tt> 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.
This documentation just lists the methods that you are likely to need to override. See the PHPdocumentor comments for a complete API documentation.




===Class declaration===
===Class declaration===


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


<code php>
<code php>
class qim_mymodel extends question_interaction_model {
class qbehaviour_mymodel extends question_behaviour {
     // ...
     // ...
}
}
Line 68: Line 68:
===IS_ARCHETYPAL===
===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 <tt>qim_informationitem</tt>, <tt>qim_interactiveadaptedformyqtype</tt>. This is indicated by defining a constant <tt>IS_ARCHETYPAL</tt> in the class. True means that this model will be offered as an option in the UI.
Some behaviours define interactions 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 <tt>qbehaviour_informationitem</tt>, <tt>qbehaviour_interactiveadaptedformyqtype</tt>. This is indicated by defining a constant <tt>IS_ARCHETYPAL</tt> in the class. True means that this behaviour will be offered as an option in the UI.


<code php>
<code php>
Line 77: Line 77:
===required_question_definition_class===
===required_question_definition_class===


Most interaction models can only work with a particular subclasses of <tt>question_definition</tt>. For example perhpas they can only work with questions that are <tt>question_graded_automatically</tt>s. 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.
Most behaviours can only work with a particular subclasses of <tt>question_definition</tt>. For example, they may only be able to work work with questions that are <tt>question_graded_automatically</tt>s. This method lets the behaviour document that. The type of question passed to the constructor is then checked against the class name returned by this function.


Example from <tt>qim_deferredfeedback</tt>:
Example from <tt>qbehaviour_deferredfeedback</tt>:
<code php>
<code php>
     public function required_question_definition_class() {
     public function required_question_definition_class() {
Line 89: Line 89:
===get_min_fraction===
===get_min_fraction===


Returns the smallest possible [[Question_Engine_2:Design#Words_related_to_grades|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.
Returns the smallest possible [[Question_Engine_2:Design#Words_related_to_grades|fraction]] that this behaviour, applied to this question, may give. Normally this will be the min fraction or the question type, however some behaviours (for example CBM) manipulate the question scores, and so need to return an adjusted min_fraction.


For example, from <tt>qim_deferredfeedback</tt>:
For example, from <tt>qbehaviour_deferredfeedback</tt>:
<code php>
<code php>
     public function get_min_fraction() {
     public function get_min_fraction() {
Line 98: Line 98:
</code>
</code>


From <tt>qim_qim_deferredcbm</tt>:
From <tt>qbehaviour_deferredcbm</tt>:
<code php>
<code php>
     public function get_min_fraction() {
     public function get_min_fraction() {
Line 109: Line 109:
===get_expected_data===
===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.
Some behaviours 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 <tt>get_expected_data</tt> 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 <tt>qim_deferredcbm</tt>
The <tt>get_expected_data</tt> 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 <tt>qbehaviour_deferredcbm</tt>
<code>
<code>
     public function get_expected_data() {
     public function get_expected_data() {
Line 124: Line 124:
===init_first_step===
===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.
You are unlikely to need to override this method. This is called when the question attempt is actually stared. It gives the behaviour 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:
From the base class:
Line 138: Line 138:
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 <tt>init_first_step</tt>.)
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 <tt>init_first_step</tt>.)


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.
When the method is called, a pending attempt step is passed in. This method can either return <tt>question_attempt::DISCARD</tt>, to indicate nothing interesting happened, and the pending step should be discarded. Or, it can update that step, and return <tt>question_attempt::KEEP</tt>, 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.
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.
Line 155: Line 155:
</code>
</code>


Note that there are some special actions that every model must be able to handle:
Note that there are some special actions that every behaviour 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.
; behaviour_var comment => 'A manual comment' : This may also have behaviour_var mark and behaviour_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.
; behaviour_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 <tt>process_comment</tt> method in a way that should be suitable for most models. It also provides a <tt>process_save</tt> implementation that may be suitable for most models.
The base class implements the <tt>process_comment</tt> method in a way that should be suitable for most behaviours. It also provides a <tt>process_save</tt> implementation that may be suitable for most behaviours.




===adjust_display_options===
===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.
This method is called before a question is rendered, so that the behaviour 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
For example, the base class does
Line 190: Line 190:
This is a block of output in the question formulation area. It goes after all the question type output in this area.
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.
Two examples. This method is used by the interactive behaviour 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===
===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.
This is a block of output in the feedback area of the question. It is used, for example in interactive behaviour, to show the '''Try again''' button.




Line 202: Line 202:
For a general introduction, see the [[Unit_tests|Moodle unit testing documentation]].
For a general introduction, see the [[Unit_tests|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 <tt>qim_walkthrough_test_base</tt> which provides a lot of helper methods to facilitate writing tests like that.
Most of the behaviours are currently tested by working one or more test questions through a sequence of example inputs. To do this they subclass <tt>qbehaviour_walkthrough_test_base</tt> 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.
The best way to start is probably to look at the tests for some of the standard behaviours.





Revision as of 16:58, 26 April 2010

This page explains how to go about writing a new Question Behaviour 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 Behaviours, you are highly encouraged to read the code of some of the existing behaviours in the Moodle code base. The code for most behaviours 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 behaviour plugin lives in a folder in question/behaviour. The layout inside that folder follows the typical layout for a Moodle plugin. For example, inside question/interaction/mymodel/ we would have:

behaviour.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/qbehaviour_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 Behaviours 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 a behaviour to have a settings.php file, so that it can have configuration options.


Question behaviour class

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

This documentation just lists the methods 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 qbehaviour_. You must extend the question_behaviour base class, or another model class.

class qbehaviour_mymodel extends question_behaviour {

   // ...

}

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 behaviours define interactions 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 qbehaviour_informationitem, qbehaviour_interactiveadaptedformyqtype. This is indicated by defining a constant IS_ARCHETYPAL in the class. True means that this behaviour will be offered as an option in the UI.

   const IS_ARCHETYPAL = true; // or false


required_question_definition_class

Most behaviours can only work with a particular subclasses of question_definition. For example, they may only be able to work work with questions that are question_graded_automaticallys. This method lets the behaviour document that. The type of question passed to the constructor is then checked against the class name returned by this function.

Example from qbehaviour_deferredfeedback:

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


get_min_fraction

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

For example, from qbehaviour_deferredfeedback:

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

From qbehaviour_deferredcbm:

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


get_expected_data

Some behaviours 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 qbehaviour_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 behaviour 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 question_attempt::DISCARD, to indicate nothing interesting happened, and the pending step should be discarded. Or, it can update that step, and return question_attempt::KEEP, 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 behaviour must be able to handle:

behaviour_var comment => 'A manual comment'
This may also have behaviour_var mark and behaviour_var maxmark, if the question is graded. This is the action that is fired when a user manually grades a question.
behaviour_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 behaviours. It also provides a process_save implementation that may be suitable for most behaviours.


adjust_display_options

This method is called before a question is rendered, so that the behaviour 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 behaviour 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 behaviour, to show the Try again button.


Unit tests

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

Most of the behaviours are currently tested by working one or more test questions through a sequence of example inputs. To do this they subclass qbehaviour_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 behaviours.


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