Monday, 29 July 2013

A better In-Progress portlet


The In-Progress Learning Activity portlet that is available with Saba 5.x offers a compact view of your recent registrations to offerings and plans, but it lacks grouping courses by certification or curriculum when a registration is made for an offering of a course that is part of a certification or curriculum. Basically, the list of In-Progress activities displayed in the portlet is flat and sorted by date or alphabetically, as in the following picture.



But what if our training framework is strongly based on certifications for regulatory compliance, for example, and listing courses without a structure is simply not good enough? Or simply courses that form a more complex module have titles like a generic “Etiquette” that taken outside of its context (i.e. the certification) mean little to the student? What we want, in short, it’s something like the portlet in the next picture, a structured visualisation of course registrations.




A custom brand new portlet

How to turn the standard In-Progress portlet into the structured one, then? The answer is, guess what, to customise the original portlet and introduce grouping by retrieving the full structure of certification / path / module and courses.

The layout of a portlet is controlled by a manager (a Java class) and a set of Saba pages. The manager class for the In-Progress portlet is called InProgressLearningActivitiesPortletManager, and you can find it in the com.saba.client.learning.blendedenrollment package of the Saba’s Java Application Archive (JAR).

Customising the manager class for the new portlet layout is very simple, as the full portlet framework is offered “for free” by the Saba API. What we need to do in our new Java class, which for convenience I’m going to call InProgressLearningActivitiesPortletManager2, is to extend the abstract manager AbstractPortletPageManager and implement the interface PortletPageManager. Then, simply define the init() method and register the Saba controller page (the one with .rdf extension) that is used to display the new portlet.

public class InProgressLearningActivitiesPortletManager2
   extends AbstractPortletPageManager
   implements PortletPageManager
{
   protected void init()
      throws SabaException
   {
      registerPage("showDefaultDisplay",
         "/learning/portlet/inProgressLearningActivities2.rdf");
   }
}

In the example above, the new controller page registered by the manager class is called inProgressLearningActivities2.rdf, and, as you can guess, it’s a copy of the inProgressLearningActivities.rdf page available out of the box in the system, with the necessary changes to reference the new portlet structure, as described in the next section.


Creating the structure

The controller page, in a pure MVC (Model – View – Controller) pattern, defines which page contains the model (i.e. the logic to retrieve data from the database and the list of widgets used for displaying it on the screen), which page is the view (i.e. the layout of all defined widgets to display), and the model object (i.e. the Java class with the actual business logic).

This is an example of the inProgressLearningActivities2.rdf used for defining the new custom In-Progress portlet. Particularly relevant are the <wdk:model> tag that references the new model page with .xml extension, the <wdk:view> tag that references the new view page with .xsl extension, and the <wdk:modelObject> tag that references the fully qualified name of the model Java class.

<?xml version="1.0" encoding="UTF-8"?>
<?cocoon-process type="portlet"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:wdk="http://www.saba.com/XML/WDK">
   <rdf:Description id="41078" pageCategory="/learning/portlet">
      <rdf:type resource="http://www.saba.com/XML/WDK/Control"/>
      <wdk:version>1.0</wdk:version>
      <wdk:model rdf:resource="inProgressLearningActivities2.xml"/>
      <wdk:view rdf:resource="inProgressLearningActivities2.xsl"/>
      <wdk:widgets rdf:resource="/wdk/xsl/widget/wdk_widgets.xsl"/>
      <wdk:modelObject>com.saba.client.learning.blendedenrollment. InProgressLearningActivitiesModel2</wdk:modelObject>
      <wdk:links> ... </wdk:links>
   </rdf:Description>
</rdf:RDF>

Basically, we have a new set of Saba pages that defines the layout of the In-Progress portlet v2, a new portlet manager and a new model class. Where and how do we define the structure, then? That’s pretty simple: the structure of courses grouped by certification or curriculum (together: by education plan) is defined in the model class, and the layout of how they show up on the screen within the portlet is defined in the Table widget used by the Saba model and view pages to represent data in a tabular format.

Starting from the former point (the Table widget), its definition is in the model .xml page. The trick here is to receive some piece of information from the model class that tells me that the specific course being displayed in the portlet belongs to a certification or a curriculum. If so, then indent its visualisation on the right.

<wdk:table name="simpleTable">
   <row path="InProgressLearningItems/LearningItem">
      <column>
         <xsp:logic>
         // If the course is part of a certification or curriculum, indent the title in the display
         if (WDKDomUtils.getNodeTextValue(wdkwidgetNode, "isInEducationPlan", wdkWidgetMaster.getXPathCache()).equals("true")) {
         </xsp:logic>
            <span class="indent">&#xa0;</span>
         <xsp:logic>
         }
         </xsp:logic>

You can read the code above in this way:

  1. Define the Table widget that displays course registrations in the portlet.
  2. For each row, the first column of the table contains a conditional logic depending on the value of the isInEducationPlan variable; this variable is defined in the model class (more later) and assumes value “true” if the course is part of a certification or curriculum, otherwise it is “false”.
  3. If true, add a <span> tag to the output for indenting the title of the course as it visually shows as belonging to a certification or curriculum. MVC-purely speaking, the HTML layout should be defined in the view .xsl page, but limitations in the Saba’s implementation of the MVC pattern allow for HTML code to be specified in the model page instead (purists are horrifying here now…).



The portlet model

Now that we have defined where to visualise the certification and course structure, we need to prepare this structure and assign the correct value to the isInEducationPlan variable. This variable is the key for building the structured layout; as said, when its value is “true”, which happens only for courses that belong to a certification, the portlet will display the course title indented under the certification itself. When “false”, the record to display is a certification itself (hence, no indenting is required) or is a loose course not part of any education plan.

Adding the isInEducationPlan variable to the XML output that the model class transfers to the model page is a matter of “visiting” a new <isInEducationPlan> tag for each processed record. If the record is a course and it belongs to an education plan (i.e. either a certification or a curriculum), the value of isInEducationPlan is true, else it is false. It sounds complicated, and… it is :-). But let’s try to make it simple by first looking at the implementation code below. A few pointers: a model class should extend the abstract class AbstractModelObject and implement the doExecute() method. This method is invoked by the model page by passing the HTTP servlet request, which represents the context where the page operates, and an XML visitor, which is used to build the output information returned to the model page. Adding a new XML tag to the output is simply a matter of visiting a new tag and writing its value, by using the visit() method of the visitor object.

public class InProgressLearningActivitiesModel2
   extends AbstractModelObject
{
   public void doExecute(HttpServletRequest req, IXMLVisitor visitor)
   {
      BlendedEnrollmentViewUtil util = new BlendedEnrollmentViewUtil();
      Collection items = util.getBlendedViewItems();
      Iterator itemIter = items.iterator();
 
      while (itemIter.hasNext()) {
         BlendedAbstractDetail itemDetail = (BlendedAbstractDetail)
         itemIter.next();
 
         // item is a course and belongs to an education plan
         if (itemDetail instanceof BlendedEnrollmentDetail &&
            isInEducationPlan(itemDetail)) {
            visitor.visit(null, "isInEducationPlan", "true");
         }
         else {
            visitor.visit(null, "isInEducationPlan", "false");
         }
      }
   }
}

Read the code above as follows:

  1. The BlendedEnrollmentViewUtil class provides a utility for retrieving In-Progress registrations for a given user as a collection of BlendedAbstractDetail objects.
  2. The code iterates over the collection and if the current item is an instance of the BlendedEnrollmentDetail object, i.e. it’s a registration to an offering for a course AND the course is part of an education plan, it creates an isInEducationPlan tag and assigns the value “true”.
  3. Otherwise, for any other item type and for courses that are not part of an education plan, the isInEducationPlan tag contains the value “false”.



Adding the new portlet to the platform

To add the new portlet to the system, simply follow these steps:

  1. In System Administration > General Configuration > Portlets, add a new portlet. The important parameter to set is PageManagerClass, which should be the fully qualified name (i.e. including the package name) of the personalised portlet manager Java class.
  2. From the Portals page, add the newly created portlet to the “mysaba” portal (this is the default portal for learners that is displayed after log in).



All done, enjoy it!

Lijepi pozdrav,
Stefano