Extend task context menu

In this tutorial, we will write a simple extension, which adds a new menu item to the context menu of a task. The item will assign the current task to the current person. So it's a shortcut for "assign this task to me".

Topics discussed:

  • Config files
  • Context menu
  • Action controller
  • Models
  • JavaScript
  • CSS


Severity: medium / programmer
Required skills: Some knowledge of PHP, JavaScript, MVC and CSS
Duration: About 30-60min to read and rebuild the example by yourself

This tutorial is based on the tutorial extension.

Resulting files: https://code.todoyu.com/browser/extensions/tutorial/task_contextmenu/trunk

For ensuring an optimal loading performance, the autoloader of todoyu is caching file paths, there for you have to clear the cache when you create a new class. To delete the cache, use the clear cache button from the Dev headlet which comes with the developer tools extension, or just delete ALL files inside the cache folder of todoyu.

 

1. Register a callback

To add a menu item, we have to register our own callback for the task context menu event.

Parameters:

  1. Type of the context menu
  2. Reference to a class method as a string
  3. Priority of the callback. Lower = sooner. So you can make sure your callback comes after another callback.


Open the file config/init.php and add this line

init.php

TodoyuContextMenuManager::addFunction('Task', 'TodoyuTutorialTaskManager::getContextMenuItems', 100);

2. Add the callback function

Create a new file TodoyuTutorialTaskManager.class.php in the model folder for the callback function.

todoyu is an MVC based application. The models contain the business logic of the application and are stored in the "model" folder.

TodoyuTutorialTaskManager.class.php

class TodoyuTutorialTaskManager {
 
	public static function getContextMenuItems($idTask, array $items) {
		return $items;
	}
 
}

The callback function receives the task ID as first parameter and an array with the current context menu items as second. So you can add, modify or remove elements from the menu. You have to return the modified version of the items array (even if you didn't change anything).

3. Prepare the context menu items

To keep the callback method small, we predefine the context menu item in the file config/contextmenu.php. Add the following configuration to this file. todoyu will automatically load any contextmenu.php file from all extensions config folder when needed.

Todoyu::$CONFIG['EXT']['tutorial']['MyContextMenu'] = array(
	'assignToMe' => array(
		'key'		=> 'assignToMe',
		'label'		=> 'tutorial.ext.contextmenu.assignToMe',
		'jsAction'	=> 'Todoyu.Ext.tutorial.assignTaskToMe(#ID#)',
		'class'		=> 'taskContextMenu taskAssignToMe',
		'position'	=> 55
	)
);

4. Add a label

The label key of the menu item references the label tutorial.ext.contextmenu.assignToMe. Todoyu will look for this label in the tutorial extension in the file ext.xml. In there should be a key named contextmenu.assignToMe with the translation.

Open the file locale/en_GB/ext.xml and add an english label for this key:

<label index="contextmenu.assignToMe">Assign to me</label>

To improve todoyus performance, the labels are cached. So you have to clear the cache first. Delete everything inside of the folder cache/labels.

5. Add the item to the context menu

Back in the callback function (models\TodoyuTutorialManager.class.php), we add the following code which adds the configured item to the menu, if the task is a normal task (not a container), is not already assigned to the current user, the status is not "cleared" and the task is not locked.

TodoyuTutorialTaskManager.class.php

public static function getContextMenuItems($idTask, array $items) {
	$idTask	= intval($idTask);
	$task	= TodoyuProjectTaskManager::getTask($idTask);
 
	if( $task->isTask() ) {
		if( ! $task->isCurrentPersonAssigned() ) {
			if( ! $task->hasStatus(STATUS_CLEARED) ) {
				if( ! $task->isLocked() ) {
					$items['assignToMe'] = Todoyu::$CONFIG['EXT']['tutorial']['MyContextMenu']['assignToMe'];
				}
			}
		}
	}
 
	return $items;
}

6. Test the menu

Now you can already click on a task which fulfills the criteria we just implemented in the callback method. You should see your item, but with a bad positioned icon of change-status. Clicking will cause a javascript error.

7. Add some style

First we add our icon to the extension icon sprite.

Open the file asset/img/icons.png in your graphic editor and copy a good icon (16x16) to the upper left corner (0/0) of the icons.png and save it.

todoyu uses a raster of 30x30 Pixels. We use only one column of icon. The next icons would start at 0/30, 0/60, 0/90

Open asset/css/ext.css and add the following styles for the new menu item.

ext.css

#contextmenu .taskContextMenu.taskAssignToMe {
	background-image:url(../img/icons.png);
	background-position: 3px 1px;
}

8. Add javascript foo

The error on clicking is caused, because the function is still missing. So lets add the function to asset/js/Ext.js in the tutorial object.

Ext.js

assignTaskToMe: function(idTask) {
	var url		= Todoyu.getUrl('tutorial', 'task');
	var options	= {
		parameters: {
			action:	'assignToMe',
			task:	idTask
		},
		onComplete: this.onAssignedToMe.bind(this, idTask)
	};
 
	Todoyu.send(url, options);
},
 
onAssignedToMe: function(idTask, response) {
	Todoyu.notifySuccess('The task is now assigned to you');				
	Todoyu.Ext.project.Task.refresh(idTask);
}

Now we have a method which sends an ajax request to the task action controller with the action assignToMe. The second function will be called when the request is completed.

9. Add the action controller

To react on the request, we create a new action controller named task with a method to handle the assignToMe action.

Create the file controller/TodoyuTutorialTaskActionController.class.php with the following code

TodoyuTutorialTaskActionController.class.php

class TodoyuTutorialTaskActionController extends TodoyuActionController {
	public function assignToMeAction(array $params) {
		
	}
}

todoyu will automatically route the request we send from javascript to this method. The return value of this method will be the response.

10. Implement the action

The action controller receives the task ID as parameter. We extract and validate the ID parameter and hand down the task ID to a model class which will update the task. So we can keep the controller simple and separate the different parts of the application.

TodoyuTutorialTaskActionController.class.php

public function assignToMeAction(array $params) {
	$idTask	= intval($params['task']);
 
	TodoyuTutorialTaskManager::assignTaskToUser($idTask);
}

11. Implement the model method

We already have the model class. It adds the item to the context menu. Now we implement here the called method which updates the task.

TodoyuTutorialTaskManager.class.php

public static function assignTaskToUser($idTask, $idPerson = 0) {
	$idTask		= intval($idTask);
	$idPerson	= Todoyu::personid($idPerson);
 
	$data		= array(
		'id_person_assigned'	=> $idPerson
	);
 
	TodoyuProjectTaskManager::updateTask($idTask, $data);
}

The second parameter $idPerson is optional and Todoyu::personid() will give us the ID of the currently logged-in person if the ID is not set.

12. Result

We have now extended the task context menu. It allows us to take over a task very quickly. The most important part is, we have not modified any existing code of another extension.