Introduction

The following provides an overview of how to create and manage interactive menu items in the side bar of the ExamSys user interface. It will also explain how to implement new menu items using the available templates, JavaScript functionality and data classes as well as how to do this for other pages.

The following applies to versions 7.70 and above

Main Components Overview

The sidebar system consists of three main components:

Creating Menu Item Data Classes

Data Class Structure

/**
* Data class that handles the creation of menu items for your page context
* 
* @author Your Name
* @copyright Copyright (c) 2024 The University of Nottingham
*/
class YourMenuItemData
{
	/** @var array Language strings used for menu item text */
	private $string;	

	/** @var string The root path of the installation */	
	private $rootPath;

	/**	
	* Constructor for YourMenuItemData
	*
	* @param array $string Array of language strings for menu items
	*/
	public function __construct(array $string)
	{
		$this->rootPath = Config::get_instance()->get('cfg_root_path');
		$this->string = $string;	
	}

	/**
	* Generate methods for each menu item following the standard format:
	* - Clear method name describing the item's purpose
	* - Return array with standard menu item structure
	* - Include relevant parameters for item generation
	*/
	public function getYourMenuItem(array $params): array
	{
		return [
			'classes' => 'yourclasses',
			'icon' => $this->rootPath . '/artwork/your_icon.png',
			'text' => $this->string['yourmenuitem'],
			// ... additional properties
		];	
	}
}

Key Principles for Data Classes

Consistent Structure

Documentation

Language Support

Template Structure

Basic template organisation

New templates pertaining to the sidebar are to be created inside the sidebar directory which is nested inside the templates directory:

templates/ └── sidebar/ ├── menuitem.html # Standard menu items ├── summativemenuitem.html # Summative checklist items └── yournewmenuitem.html # Custom template variations

Implementing Menu Items

Create the data structure

First, we create the menu item data structure inside of its respective data class:

// In your data class (e.g., PaperMenuItemData)
public function getEditPropertiesItem(int $paperID, string $module, string $folder): array
{
	return [
		'classes' => 'properties',
		'icon' => $this->rootPath . '/artwork/properties_icon.gif',
		'text' => $this->string['editproperties'],
		'href' => $this->rootPath . "/paper/properties.php?paperID=$paperID&caller=details&module=$module&folder=$folder",
		'action' => 'directUrl'
	];
}

Render on the page

We then render the menu item on the page that it is needed in below is how something like this may look like, taking the papertasks menu as an example:

// Initialize required objects
$menuItemData = new PaperMenuItemData($string);
$render = new render($configObject);

// Create a menu section
echo '<div class="submenuheading" id="papertasks">' . $string['papertasks'] . '</div>';
echo '<div id="menu1" role="menu">';

// Render individual items
$testPreviewItem = $menuItemData->getTestPreviewItem($properties, $properties->get_crypt_name());
$render->render($testPreviewItem, $string, 'sidebar/menuitem.html');

$editPropertiesItem = $menuItemData->getEditPropertiesItem($paperID, $module, $folder);
$render->render($editPropertiesItem, $string, 'sidebar/menuitem.html');

echo '</div>';

Menu Item Actions Guide

Menu items in the sidebar use a data-action attribute to define their behaviour. These actions are then handled by the handleMenuItemAction function in the sidebar.js file.

Core Action Types

Direct URL Navigation (directUrl)

Used for direct page navigation. The action looks for an anchor tag inside of a menu item and then navigates to its href value.

Required properties:

// In your data class
public function getDirectUrlItem(): array 
{
	return [
		'classes' => 'properties',
		'icon' => $this->rootPath . '/artwork/icon.gif',
		'text' => $this->string['menutext'],
		'href' => $this->rootPath . '/path/to/page.php',
		'action' => 'directUrl'
	];
}

// How it's handled in sidebar.js
case 'directUrl':
	var href = menuItem.find('a').attr('href');
	if (href) {
		window.location = href;
	}	
	break;

Popup Windows (openPopup)

Opens a new window popup on the same page.

Required data_attributes:

Window feature options:

public function getPopupItem(): array 
{
	return [
		'classes' => 'popupclass',
		'icon' => $this->rootPath . '/artwork/popup_icon.gif',
		'text' => $this->string['popuptext'],
		'action' => 'openPopup',
		'data_attributes' => [
			'url' => $this->rootPath . '/popup/content.php',
			'popuptype' => 'window',
			'name' => 'previewWindow',
			'features' => 'width=800,height=600,scrollbars=yes',
			'focus' => true
		]	
	];
}

case 'openPopup':
	var settings = menuItem.data();
	if (settings.popuptype === 'window') {
		var popup = window.open(settings.url, settings.name, settings.features);
		if (settings.focus && window.focus) {
			popup.focus();
		}
	}
	break;

Submenus (openSubMenu)

Creates cascading menu functionality. Used when a menu item needs to display additional options.

Required properties:

Required data_attributes:

public function getSubmenuItem(): array 
{
	return [
		'classes' => 'cascade showmenu',
		'icon' => $this->rootPath . '/artwork/submenu_icon.gif',
		'text' => $this->string['submenutext'],
		'href' => '#',
		'action' => 'openSubMenu',
		'type' => 'button',
		'hasPopup' => true,
		'popupType' => 'menu',
		'data_attributes' => [
			'popupid' => '1', // Unique identifier for the submenu
			'popuptype' => 'papertasks',// Type of submenu
			'popupname' => 'submenu1' // Name for reference
		]	
	];
}

case 'openSubMenu':
	var options = JSON.parse($("#popupmenu" + menuItem.attr('data-popupid')).attr('data-myOptions'));
	var urls = JSON.parse($("#popupmenu" + menuItem.attr('data-popupid')).attr('data-myURLs'));
	var id = 'popup' + menuItem.attr('data-popupid');
	var type = menuItem.attr('data-popuptype');
	var name = menuItem.attr('data-popupname');
	this.showMenu(id, type, name, options, urls, e);
	break;

Adding New Actions

You can extend the system with custom actions by-

  1. Defining the action inside you menu item data:

public function getCustomActionItem(): array 
{
	return [
		'classes' => 'customaction',
		'icon' => $this->rootPath . '/artwork/custom_icon.gif',
		'text' => $this->string['customtext'],
		'action' => 'myCustomAction',
		'data_attributes' => [
			'custom-data' => 'value',
		]
	];	
}

      2. Adding the case handler inside the sidebar.js file.

case 'myCustomAction':
	var customData = menuItem.data('custom-data');
	// Implement your custom behavior
	break;	

Prior to ExamSys 7.7.0

The following applies to versions prior to 7.7.0

A number of sidebar menus have different states depending on what has been selected. For example, when in a paper initially most of the 'Question Tasks' options are greyed out. These become active links once the user has selected a question. To do this all the links within 'Questions Tasks' are held in two <DIV> containers: one with the greyed out options and one with the active links. When the user selects a question JavaScript hides the grey <DIV> and shows the active link <DIV>.

The sidebar menus are contained within include files, brought in from /include/ directory. The language strings that are needed for the sidebar menu should be included in the 'parent' PHP file, they do not need their own separate language include file.