Help
Custom Activity Report Example

This example demonstrates how to build a custom activity report. It features Frame API and DLAP calls from Javascript.

You can download the component source code from Report1.zip.

The report contains student views, time spent, and scores. The report is implemented as a custom component, whose rendering looks similar to this:

activity report

Creating the Report

The report component is a VHTML file that expects two input parameters: enrollmentid and courseid. The report includes jQuery, an open-source Javascript library that simplifies HTML document handling. The report depends on the containing BrainHoney Frame to provide the Frame API.

1.

Create a file named report1.vhtm. Include jQuery and define some CSS styles that the report can use. Because the report is intended to be displayed within another HTML page (not in an iframe), it is an HTML fragment, without the outermost html and body tags.

<!-- Include jQuery for easy HTML manipulation -->
<script type="text/javascript" 
  src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.1.min.js"></script>

<!-- Define some CSS styles  -->
<style type="text/css">
.report-table
{
	border-style:none;
	border-color: Black;
	border-collapse:collapse;
	margin-left: 15px;
	margin-top: 30px;
}
.table-title
{
	background-color:#D5D5D5;
	color: Navy;
	font-weight:bold;
}

.table-subtitle
{
	background-color: #EDEDED;
	color: Navy;
	font-weight:bold;
}
.report-table td
{
	padding:5px 10px 5px 10px;
	border: 1px solid black;
	text-align: center;
}
.report-table .student-name
{
	text-align:left;
}
</style>
2.

Create the table, the first two rows, and the first empty cell of each row. We'll dynamically add the other rows and columns later.

<table class='report-table'>
  <tr class="table-title">
    <td style="border-style:none;" class="page_color"></td>
  </tr>
  <tr class="table-subtitle">
    <td style="border-style:none;" class="page_color"></td>
  </tr>
</table>
3.

Create a Javascript script tag, and within it, create an encapsulation function that will contain all our report's Javascript. This prevents collisions between our functions and other objects/functions in the global scope.

<script type="text/javascript">
(function() {

  // All other Javascript logic will go here...

})();
4.

Define a function that jQuery executes when the DOM is ready for manipulation. This function calls the FRAME_API to set the page title (setComponentState). It then executes two DLAP commands (executeCommand) in a single AJAX request: one command (getmanifest) to get the course manifest, and one command (getentitygradebook3) to get the gradebook data for all students in the course.

Upon completion, executeCommand calls the buildScoresTable function, which we define later.

// a jQuery method that executes when the page is ready for manipulation
  $(document).ready(function() {
      initLayout();
  });
    
  // Initializes the page layout
  function initLayout() {
      // Set the title of the page
      FRAME_API.setComponentState('report1', '$ID$', {
          pageTitle: 'Report 1',
          subTitle1: 'Item Views and Time'
      });

      // Construct XML for 2 DLAP requests. enrollmentid and courseid are both input 
      // paramters to this component. Their values get automatically replaced 
      // by BrainHoney
      var data = '<batch>' + 
          '<request cmd="getmanifest" entityid="$enrollmentid$" />' +
          '<request cmd="getentitygradebook3" entityid="$courseid$" itemid="**" />' +
      '</batch>';
      FRAME_API.executeCommand('', null, {method: 'POST', callback: buildScoresTable, 
        xmlData: data});
  }
5.

Define the function buildScoresTable, which reads XML returned from DLAP, adds column headers for each gradable activity in the course, and adds rows for each enrolled student. It calls some helper functions to format the scores and time-spent values.

// Builds the table of data for this report
function buildScoresTable(options, success, response) {
    // Check for error
    if (!success) {
        $('.table-title').append('<td>Error</td>');
        return;
    }

    var titleRow = $('.table-title');
    var subTitleRow = $('.table-subtitle');
    var columns = [];

    // Add column headers for appropriate course items using XML returned from 
    // getmanifest DLAP call. Get all visible non-folder 'item' elements from 
    // the getmanifest XML data
    $('manifest item', response.responseXML).filter(function (index) {
        var itemType = $('> data > type', this);
        return itemType.length > 0 && itemType.text() != 'Folder';
    }).each(function (index, item) {
        // Get the data from the XML we use to display the row
        var itemId = item.getAttribute('id');    // get the item id
        var dataNode = $('> data', item);        // get the 'data' element within item
        var title = $('title', dataNode).text(); // get the item title
        var itemType = $('type', dataNode).text();// get the item type

        // List of items we have columns for. We show scores only for 
        // assessments in this report.
        var column = {
            itemId: itemId,
            showScore: itemType == 'Assessment'
        };
        columns.push(column);

        // Add a header and subheader cells for this item
        if (column.showScore) {
            titleRow.append('<td colspan="3">' + title.htmlEncode() + '</td>');
            subTitleRow.append('<td>No.</td><td>Time</td><td>Score</td>');
        } else {
            titleRow.append('<td colspan="2">' + title.htmlEncode() + '</td>');
            subTitleRow.append('<td>No.</td><td>Time</td>');
        }
        return true;
    });


    // Add a row for each student enrollment with their accompanying scores using 
    // XML returned from getentitygradebook3 DLAP call
    var rows = [];
    $('enrollments enrollment', response.responseXML).each(
      function(index, enrollment) {
        rows.push('<tr>');
        
        // Add the student-name cell
        var user = $('user', enrollment)[0];
        var displayName = user.getAttribute('firstname') + ' ' + 
          user.getAttribute('lastname');
        rows.push('<td class="student-name">' + displayName + '</td>');

        // Add a cell for attempts, time, and (optional) score using this 
        // enrollment's XML data
        for (var i = 0; i < columns.length; i++) {
            // Find matching item element for the current column
            var itemData = $('item[itemid=' + columns[i].itemId + ']', enrollment);
            if (itemData.length > 0) {
                addCell(rows, itemData[0].getAttribute('attempts'));
                addCell(rows, formatSeconds(itemData[0].getAttribute('seconds')));
                if (columns[i].showScore) {
                    addCell(rows, formatScore(itemData[0].getAttribute('achieved'), 
                      itemData[0].getAttribute('possible')));
                }
            } else {
                // This enrollment has no data for this item. All cells empty.
                addCell(rows, '-');
                addCell(rows, '-');
                if (columns[i].showScore) {
                    addCell(rows, '-');
                }
            }
        }
        rows.push('</tr>');
    });

    // Display this if there are no students in this course.
    if (rows.length == 0) {
        rows.push('<tr><td>No data to report.</td></tr>');
    }

    // Add the data to the table
    $('.report-table').append(rows.join());
}

// Helper function that adds HTML for a table cell to the input buffer
function addCell(buffer, data) {
    buffer.push('<td>' + (data || '-') + '</td>');
}

// Helper function that returns (achieved / possible) as a percentage truncated 
// to 1 decimal place
function formatScore(achieved, possible) {
    if (achieved && possible) {
        return Math.floor((achieved / possible) * 1000.0) / 10;
    } 
    return '-';
}

// Helper function that converts number of seconds to a minutes:seconds
function formatSeconds(seconds) {
    if (seconds) {
        var timeSpan = Math.floor((seconds / 60)).toString() + ':';
        if ((seconds % 60) < 10) {
            timeSpan += '0';
        }
        return (timeSpan + (seconds % 60).toString());
    }
    return '-';
}

// Helper function that returns the HTML encoded version of a string
String.prototype.htmlEncode = function () {
    return $('<div/>').text(this.toString()).html();
};

That completes the page definition. To test it, you must upload it to a domain and then launch it, as described in the following sections.

Uploading the Report

You upload the report1.vhtm file to your BrainHoney domain content folder.

1.
admin gear

Log into BrainHoney with a domain administrator account and click the Administrator gear icon to open the administration page.

2.
content tab

Click the Content tab to display the domain's content files.

3.
upload content dialog

Click the Upload Content button, choose the saved report1.vhtm file and then click [OK]. Or, if you are familiar with FTP, you can simply upload the file to the FTP URL displayed in the bottom right corner of the Content tab.

Launching the Report

Since the report is a component, you can launch the report with a URL that follows the component URL Syntax.

Our report expects as input parameters a course ID and a teacher's enrollment ID in that course. Assuming our domain name is helmon, our domain ID is 531124, our course ID is 22222, and our enrollment ID is 33333, the component URL would be:

http://helmon.brainhoney.com/Frame/Resource/531124/report1.vhtm?enrollmentid=33333&courseid=22222

You can also launch the report component from a <%Render%> tag in another VHTML file from the same domain like this:

<%Render Href="Report1.vhtm" enrollmentid="33333" courseid="22222" %>