LCMS Defining your own learning object type

From Dokeos

Jump to: navigation, search

LCMS_Developer_Cookbook

Original author: Tim De Pauw

Contents

Synopsis

Learning objects used in Dokeos all belong to a learning object type. In the spirit of object-oriented programming, all types extend the basic learning object type. To define your own type, all you have to do is provide a couple of files that define the type. Because you are extending the basic learning object type, there is no need to worry about the properties and behavior that are already provided by this type —more to the point: objects that belong to your type will automatically offer functionality for getting and setting their title, description, creation and modification date, etc. In addition, loading and saving objects is automatically taken care of. We will guide you through creating your own learning object type through the example of the fictitious type memo. A memo will be a learning object much like a standard announcement, but, in addition, it will have a property priority, a number between 1 and 5, indicating how important the memo is.


Recipe

  1. First off, each learning object type is defined by a directory that resides under repository/lib/learning_object. A type name may only consist of lowercase characters and underscores. So, let’s create a directory for the memo type: repository/lib/learning_object/memo.
  2. Next, we need to fill the directory with some meaningful files. First, let’s tell Dokeos which additional properties the new learning object type should have. Again, we recommend using only lowercase characters and underscores for the property names. The memo type only has one property, but you can have as many as you like. You name the file after the type name, followed by .xml. For instance, the memo type property definition should be named repository/lib/learning_object/memo/memo.xml. This XML file not only defines the properties of this learning object type, but also tells the installer which storage units should be created. The XML-file needed for our memo learning object type can be found below.
  3. Now, it’s time for some PHP. First, we need a class definition file for the type. That one will have the extension .class.php. While the file name is all lowercase, the class name is in something called ‘camel case’ (or ‘CamelCase’), which you may be familiar with already. Basically, it means you capitalize each word instead of separating words with underscores. The class name for memo simply becomes Memo, but let’s say we had a type name cookbook_chapter; that would translate to the class name CookbookChapter. Dokeos provides a script that allows you to create trivial class files for all learning object types that lack a class definition. Hence, if you run repository/util/build_classes.php now, you will automatically own memo.class.php, complete with accessor methods for every property. However, since learning objects do not allow attachments by default, we need to tell the class that memos do allow them. As it turns out, that is easy as pie: we just have to override the static function supports_attachments() to return true. You may extend the class definition with methods that define the behavior of the new type; for now, the memo type will not offer any specific behavior. The final version of the class is available with this recipe.
  4. While we have attempted to limit the amount of code you need to come up with to a minimum, there are two classes we do need you to write yourself. The first is a display class, which will reside in (type_name)_display.class.php and be named (ClassName)Display. You will be happy to know that this is usually a pretty basic class. Simply extending the existing LearningObjectDisplay class will already give you a usable display class. You may however want to override any of the get_full_html() and get_short_html() functions to provide a type-specific view. The former provides a complete view of everything that makes up the learning object, while the latter only displays essential information about it. Both will want to use the function get_learning_object() to access the learning object that is to be displayed. Take a look at the example at the end of this recipe for sample implementations of the display functions.
  5. And finally, there’s the form class, allowing users to create new instances of your learning object type, as well as edit existing ones. The class will be called (ClassName)Form and reside in (type_name)_form.class.php. Again, you extend a class that is already there, this time LearningObjectForm. However, while the most basic display class is an empty extension, simply creating an empty extension of the form class will not provide you with a working form. We will now go over the functions of LearningObjectForm that you need to override to get your form working. In addition, this recipe comes with the full source code for MemoForm.
    1. build_creation_form($default_learning_object = null) builds a form to create a new learning object of the type. It may be passed a default learning object, setting default values for the form—you need not worry about that until later on. Your average implementation of this function does the following:
      1. Invoke the parent function, which initializes the form, adding elements for the default learning object properties.
      2. Add form elements for the properties specific to this learning object type. This is done by using Dokeos’s FormValidator API, as LearningObjectForm extends it.
      3. Invoke the add_footer() function. This actually adds an attachment selector if that feature is supported, as well as a submit button.
      4. Invoke the set_defaults() function. We will implement that in a moment.
    2. build_editing_form($object) builds a form that allows the user to edit the learning object $object. Again, there is a typical implementation, which is usually identical to the one for the creation form.
    3. set_defaults($defaults = array) sets the form’s default values, using $defaults to override any of them. As you can imagine, a typical implementation does the following:
      1. Invoke the parent function.
      2. Set default values for the type-specific form elements. The values should be based on the learning object associated with the form. Hence, you need to use the get_learning_object() function to retrieve it first. Note that its return value may be null, in which case no learning object is associated with the form and you should use sensible defaults.
    4. create_learning_object($owner) effectively creates the learning object from the values the user has filled in, and makes it persistent; the parameter $owner is the user ID of the owner of the object, which you probably should not be concerned with. What this means is that the function actually does the following:
      1. Instantiate the class that defines the learning object type.
      2. Set the values of the type-specific properties on the newly instantiated object.
      3. Invoke the set_learning_object($object) function to tell the form that $object is to be made persistent.
      4. Invoke the parent function, which instructs the data manager to create the object in persistent storage.
      5. update_learning_object($object) updates $object with the values the user has filled in, after which it calls the parent function to make the changes persistent.
  6. We are not quite there yet, but we are definitely close. In order to have room for the additional properties in persistent storage, we need to allocate that. Now, for the default data manager, all you need is a table named after the type, with a numeric column id, as well as columns for every type-specific property; the values of the default properties are stored elsewhere. Learning object types should provide a file called (type_name).xml. Take a look at the example for the memo type, which comes with this recipe.
  7. Congratulations! You have successfully tackled defining your own learning object type. Now, you should be able to surf to the repository and create learning objects of the type you have just created. Be sure to test creating, editing and viewing learning objects in particular.


Notes

  • All the file and class names mentioned in this recipe need to be respected exactly. Your learning object type will fail to load if you deviate from the given naming scheme.
  • It is considered good practice to define class constants for the property names specific to a learning object type. This way, building conditions becomes less ambiguous. For instance, searching for learning objects that have a type of memo and a priority of 3 tells you less about what is going on than doing the same but with Memo :: PROPERTY_PRIORITY equal to 3.
  • If your learning object type does not require any additional properties, you do not need a property file or a table definition file. The other files must still be present.
  • For examples of establishing parent-child relationships between learning objects, please take a look at the forum and learning_path type definitions.
  • The LearningObject class has a few more methods that you might like to override. For more information, we recommend you consult the API documentation.


Appendices

memo.class.php

<?php
require_once dirname(__FILE__).'/../../learningobject.class.php';
class Memo extends LearningObject
{
 const PROPERTY_PRIORITY = 'priority';
 function get_priority()
 {
   return $this->get_additional_property(self :: PROPERTY_PRIORITY);
 }
 function set_priority($priority)
 {
   return $this->set_additional_property(self :: PROPERTY_PRIORITY, $priority);
 }
 static function supports_attachments()
 {
   return true;
 }
}
?>

memo_display.class.php

<?php
require_once dirname(__FILE__).'/../../learningobjectdisplay.class.php';
class MemoDisplay extends LearningObjectDisplay
{
 function get_full_html()
 {
    $memo = $this->get_learning_object();
return '
' .'
' .'<img src="'.api_get_path(WEB_CODE_PATH).'img/memo.gif'.'" alt="memo"/>
' .'
'.htmlentities($memo->get_title()) .' ('.$memo->get_priority().')
' .'
'.$memo->get_description().'
' .'
'
           .$this->get_attached_learning_objects_as_html();
  }
  function get_short_html()
  {
    $memo = $this->get_learning_object();
    return ''
      .htmlentities($memo->get_title())
      .' ('.$memo->get_priority().')'
      .'';
  }
 }
?>

memo_form.class.php

<?php
require_once dirname(__FILE__).'/../../learningobjectform.class.php';
require_once dirname(__FILE__).'/memo.class.php';
class MemoForm extends LearningObjectForm
{
 function build_creation_form($default_learning_object = null)
 {
   parent :: build_creation_form($default_learning_object);
   $this->build_form();
 }
 function build_editing_form($object)
 {
   parent :: build_editing_form($object);
   $this->build_form();
 }
 function setDefaults($defaults = array ())
 {
   $memo = $this->get_learning_object();
   if (isset ($memo))
   {
     $defaults[Memo :: PROPERTY_PRIORITY] = $memo->get_priority();
   }
   parent :: setDefaults($defaults);
 }
 function create_learning_object($owner)
 {
   $memo = new Memo();
   $memo->set_priority($this->exportValue(Memo :: PROPERTY_PRIORITY));
   $this->set_learning_object($memo);
   return parent :: create_learning_object($owner);
 }
 function update_learning_object($object)
 {
   $object->set_priority($this->exportValue(Memo :: PROPERTY_PRIORITY));
   return parent :: update_learning_object($object);
 }
 private function build_form()
 {
   $options = array (1, 2, 3, 4, 5);
   $this->addElement('select', Memo :: PROPERTY_PRIORITY,
     get_lang('Priority'), $options);
   $this->set_defaults();
   $this->add_footer();
 }
}
?>

memo.xml

<?xml version="1.0" encoding="UTF-8"?>
<object name="memo">
 <properties>
  <property name="id" type="integer" unsigned="1" notnull="1"/>
  <property name="priority" type="integer" unsigned="1" notnull="1"/>
 </properties>
 <index name="id" type="primary">
  <indexproperty name="id"/>
 </index>
</object>
Personal tools