community.egroupware.org: Community wiki

  
Community wiki
The Day 6th
the day before | Up | the day after

After a quite while of not getting updated our Code Corner, we are going to back to our tutorials in order to introducing some of more amazing features that is created on Egroupware in recent years.

Briefly, reviewing our five days of tutorial, we did the setup in the day The Day first and created a small application, called test, in order to store and retrieve contact lists to our egw_test database. The application was designed through the etemplate, and used NextMatch widget template in order to list our data.
If you look at the layout of our last update of the test application (see the figure 6.0), you can see, we handled our actions on the NextMatch widget through some buttons which we called them delete and edit under the action column, and they are fairly not bad in able to edit or delete our inserted rows on the list.

(figure 6.0)
  • Notice: wherever you see red arrow on figures, it is not part of the EGroupware layout, it is drawn by me to make the figures more understandable.

These action buttons are still not very user friendly to use, though. Since, They are not quite easy to find and apply the action quickly as possible. Therefore, I would invite you to do another day of tutorial, about action (see the figure 6.1) which is quite amazing feature to handle any sort of actions as an action menu, accessible on each items of list by just a right click.

(figure 6.1)

So, for today we are going to cover these requests:
  • Some minor changes to our last code
  • A NextMatch widget list with an action menu; including 'edit' and 'delete' actions.

Since we are now mostly focused on working with etemplate, we would address our test application directly to our etemplate page rather than calling the testinterface and then clicking on the Call the eTemplate version link in order to call the index method. Simply add this line header('Location: ../index.php?menuaction=test.test_ui.index'); into the index.php, and remark the header('Location: ../index.php?menuaction=test.test_ui.testinterface'); . And we can remark or remove the whole testinterface method in the class.test_ui.inc.php file, since it is a dead code.

/index.php

---
<?php
/**
 *prepare some application information for eGroupware to know
 *If the applicationname of your application (e.g.: test) and the value of
 *currentapp does not match, you will receive an authentication error
 */
// Old exec of test_ui 
//header('Location: ../index.php?menuaction=test.test_ui.testinterface');
// Call the test.index etemplate 
header('Location: ../index.php?menuaction=test.test_ui.index'); 
---

The next thing, we want to change is, defining test_ui(), test_bo(), and test_so() constructor methods with __construct() method, therefore, we must change parent class initialization through parent::__construct().

class.test_ui.inc.php

---
// test_ui class Constructor
     function __construct()
    {
	/**
	 * Since we extended the class test_ui from test_bo we can initial the constructor of its parent class
	 * which is test_bo. 
	 * 
	 */
	parent::__construct();
---

class.test_bo.inc.php
---
// test_bo class constructor
    function __construct()
    {
	/**
	 * Since we extended the class test_bo from test_so
	 * we can initial the constructor of its parent class 
	 * which is test_so. 
	 * 
	 */
        parent::__construct();
---


class.test_so.inc.php
---
// test_so class Constructor 
	function __construct()
	{
	       /**
		* Since we extended the class test_so from so_sql 
		* we can initial the constructor of its parent class 
		* which is so_sql.
		* 
		*/
		parent::__construct(TEST_APP,$this->maintable);
---


Adding action menu to our NextMatch widget
In order to add an action menu on the NextMatch widget, we need to initiate 'actions' => $this->get_actions() and 'row_id' => 'test_id', in our $content['nm'] array which is located in the index function. The row_id is a key into row content to set it's value as tr id, in our case we need to set it to test_id.
For getting the actions' parameters (Further information), we defined the get_actions() method in class.test_ui.inc.php, which initialises our required parameters as an $actions array and return it to actions' parameter in the $content['nm'] initialization. And Since we need the two 'edit' and 'delete' actions on our action menu, we set the $actions array like this


The get_actions method in class.test_ui.inc.php
---
        /**
	 * get actions array for nextmatch_widget::get_action()
	 * basicly the get_actions method is to define the parameters and return them as the actions array 
         */
	function get_actions()
	{
		$actions = array(
			'edit' => array(
				'caption' => 'edit',
				'default' => true,
				'group'  => 1,
			),
			'delete' => array(	
				'caption' => 'delete',
				'group'  => 1,
			),
		);
		return $actions;
	}
---

but generally speaking, the array we return should look like JSON encoded:
[
{
"id": "edit",
"caption": "edit",
"default": true,
},
]

After we added all above codes in class.test_ui.inc.php, we should be able to get action on our nextmatch widget list, including two menus; edit and delete, like what you see on figure 6.1.
So, what would be the next step? Yes, you are thinking quite right. As next step, we need to define our two actions edit and delete on the menu, to do something for us. As you may remember, at the day 5th, we already have handled our edit and delete as an action buttons. Now we are going to do it slightly different with defining a new method function actions($content) in class.test_ui.inc.php like this:

---
function actions($content) 
	{
		// Handles actions from action menu
		switch ($content['nm']['action']) 
		{
			//edit 
			case "edit":
				if ($this->read($content['nm']['selected'][0]))
					{
						$content['test_id'] = $content['nm']['selected'][0];
						// merge the data array into the content, preserving non-db settings
						foreach($this->data as $db_col => $col)
						{
							$content[$db_col]=$col;
						}
					}
				break;
			//delete			
			case "delete":
				if ($this->read($content['nm']['selected'][0]))
				{
					$del = $content['nm']['selected'][0];
					//echo $del;
					$this->delete(array('test_id'=>$del));
				}
				break;
		}
		//return back the changed $content
		return $content;
	}
---

If you look at the above code, you can see, watching for the notification act of our actions on the menu handled by this $content['nm']['action'] instead of this $content['nm']['row']['edit'], and if the edit or delete select, we read the selected row(s) like this:

$this->read($content['nm']['selected'][0])
instead of this:
list($id)=each($content['nm']['rows']['edit']);

$content['nm']['selected'][0] return the id number of selected row.

Now it is time to call our actions($content) method in the index method.

---
// Call the actions function in order to execute the actions from action menu 
$content =& $this->do_actions($content);
---

Okay! Let's run the application.

Here is the whole code from class.test_ui.inc.php, and also you can download package code of the application here.

class.test_ui.inc.php
---
<?php
require_once(EGW_INCLUDE_ROOT.'/test/inc/class.test_bo.inc.php');
class test_ui extends test_bo
{
	var $public_functions = array(
		'testinterface'	=> True,
	        'index'	        => True,
		);
       /**
	* variable for instantiating an object from the etemplate class
	*
	* @var etemplate
	*/
	var $tmpl;
	
	// test_ui class Constructor
	function __construct()
        {
	      /**
	       * Since we extended the class test_ui from test_bo we can initial the constructor of its parent class
	       * which is test_bo.
	       * 
	       */
		parent::__construct();
                $GLOBALS['egw_info']['flags']['app_header']='test-Application';
		// Instantiate an object from the etemplate class 
		$this->tmpl =& CreateObject('etemplate.etemplate');
		if(!@is_object($GLOBALS['egw']->js))
		{
			$GLOBALS['egw']->js =& CreateObject('phpgwapi.javascript');
		}
	}
	
/**
 *index function 
 * 
 * function call index
 * the eTemplate version of the application. basically the same 
 * a form with two  input-fields an additional selectbox and a checkbox to enable content-debugging within a predefined area of the 
 * eTemplate
 * The function handles the behavior of the eTemplate in accordance to the content of the content array
 * If the data-handling part is completed, the eTemplate is read and executed with content and select-option arrays.
 * 
 * @param content from etemplate
 * 
 */
	function index($content=null)
	{
		// Edit action by click on edit button "edit"		
		if ($content['nm']['rows']['edit'])
		{
			echo "button edit";
			list($id)=each($content['nm']['rows']['edit']);
			if ($this->read($id))
			{
				//$content=$this->data;
				// merge the data array into the content, preserving non-db settings
				foreach($this->data as $db_col => $col)
				{
					$content[$db_col]=$col;
				}
			}
		}			
		// Delete action by click on edit button "delete"
		if ($content['nm']['rows']['delete'])
		{
			echo "button delete";
			list($del)=each($content['nm']['rows']['delete']);
			echo "<br>del $del<br>";
			$this->delete(array('test_id'=>$del));
		}
		if (!$del) // to avoid calling do_actions "edit" when click on button delete on the list occured
		{
			// Call the action function in order to execute the actions from action menu 
			$content = $this->actions($content);
		}
		// initializing of the nextmatch widget, through reading of stored sessiondata
		$content['nm']= $GLOBALS['egw']->session->appsession(@$this->called_by.'session_data','test');
		
		// if empty, or not an  array, then you have to do the initializing on your own.
		if (!is_array($content['nm']))
		{
			$content['nm'] = array(		// I = value set by the app, 0 = value on return / output
				'get_rows'       =>	'test.test_ui.get_rows', // I  method/callback to request the data for the rows eg. 'notes.bo.get_rows'
				'filter_label'   =>	'', // I  label for filter    (optional)
				'filter_help'    =>	'', // I  help-msg for filter (optional)
				'no_filter'      => True, // I  disable the 1. filter
				'no_filter2'     => True, // I  disable the 2. filter (params are the same as for filter)
				'no_cat'         => True, // I  disable the cat-selectbox
				//'template'       =>	, // I  template to use for the rows, if not set via options
				//'header_left'    =>	,// I  template to show left of the range-value, left-aligned (optional)
				//'header_right'   =>	,// I  template to show right of the range-value, right-aligned (optional)
				//'bottom_too'     => True, // I  show the nextmatch-line (arrows, filters, search, ...) again after the rows
				'never_hide'     => True, // I  never hide the nextmatch-line if less then maxmatch entrie
				'lettersearch'   => False,// I  show a lettersearch
				'searchletter'   =>	'',// I0 active letter of the lettersearch or false for [all]
				'start'          =>	0,// IO position in list
				//'num_rows'       =>	// IO number of rows to show, defaults to maxmatches from the general prefs
				//'cat_id'         =>	// IO category, if not 'no_cat' => True
				//'search'         =>	// IO search pattern
				'order'          =>	'test_dts',// IO name of the column to sort after (optional for the sortheaders)
				'sort'           =>	'DESC',// IO direction of the sort: 'ASC' or 'DESC'
				'col_filter'     =>	array(),// IO array of column-name value pairs (optional for the filterheaders)
				//'filter'         =>	// IO filter, if not 'no_filter' => True
				//'filter_no_lang' => True// I  set no_lang for filter (=dont translate the options)
				//'filter_onchange'=> 'this.form.submit();'// I onChange action for filter, default: this.form.submit();
				//'filter2'        =>	// IO filter2, if not 'no_filter2' => True
				//'filter2_no_lang'=> True// I  set no_lang for filter2 (=dont translate the options)
				//'filter2_onchange'=> 'this.form.submit();'// I onChange action for filter, default: this.form.submit();
				//'rows'           =>	//  O content set by callback
				'actions'		=> $this->get_actions(), // I  array with actions, see nextmatch_widget::egw_actions
				'row_id' 	=> 'test_id', // I key into row content to set it's value as tr id, eg. 'id' 
				//'action'		=> ,
				//'total'          =>	//  O the total number of entries
				//'sel_options'    =>	//  O additional or changed sel_options set by the callback and merged into $tmpl->sel_options
			);
		}
		else
		{
			
			
		}
		
		$debug=$content['debug'];
                $sel_options = array(
                'test_property' => $this->types
                 );

		// condition to check when the save record happens
		if (!$id && !$del && !($content['nm']['selected'][0] == 'delete') && is_array($content) && !$content['new'] )
		{ 
			// after submit
			$content['debug']=$debug;
			$content['datetime']=time();
			$content['message']='Orginalinhalt: '.print_r($content,true);
			if (trim($content['test_firstname'].$content['test_name'])!='')
			{
				$this->data['test_dts']=$this->now;
				//debuging
				// Save the content array
				$this->save($content);
				// Resetting of the data-array
				$this->init();
				$content['who']=$content['test_firstname']." ".$content['test_name'] ;
			}
			$content['message']='Nach dem Einfügen:'.print_r($sel_options,true).print_r($content,true);
		} 
		
		// To clear the fields when one of 'new', 'submit', or 'delete' action happens.
		if ($content['new'] || $content['submit'] || $del || ($content['nm']['selected'][0] == 'delete'))
		{
			$content['test_id'] = ''; //reset the test_id to create a new row 
			$content['debug']=0;
			$content['who']='';
			$content['test_name']='';
			$content['test_firstname']='';
		}
		$this->tmpl->read('test.index');
		$this->tmpl->set_cell_attribute('debuginfo','disabled',!$debug);
		
		$this->tmpl->exec('test.test_ui.index',$content,$sel_options,'',array('test_id'=>($this->data['test_id']?$this->data['test_id']:$content['test_id']))); 
	}
	
	/**
	 * get actions array for nextmatch_widget::get_action()
	 * basicly the get_actions method is to define the parameters and return them as the actions array 
	 */
	function get_actions()
	{
		$actions = array(
			'edit' => array(
				'caption' => 'edit',
				'default' => true,
				'group'  => 1,
				
			),
			'delete' => array(	
				'caption' => 'delete',
				'group'  => 1,
				
			),
		);
		return $actions;
	}
      /** 
       * action() method handels the actions choosen from the action menu on the nextmatch list
       * 
       * @param passing the array of content from etemplate contents 
       * @return array of content
       *
       */
	function actions($content) 
	{
		// Handles actions from action menu
		switch ($content['nm']['action']) 
		{
			//edit 
			case "edit":
				if ($this->read($content['nm']['selected'][0]))
					{
						$content['test_id'] = $content['nm']['selected'][0];
						// merge the data array into the content, preserving non-db settings
						foreach($this->data as $db_col => $col)
						{
							$content[$db_col]=$col;
						}
					}
				break;
			//delete			
			case "delete":
				if ($this->read($content['nm']['selected'][0]))
				{
					$del = $content['nm']['selected'][0];
					//echo $del;
					$this->delete(array('test_id'=>$del));
				}
				break;
		}
		//return back the changed $content
		return $content;
	}
	
	/**
	 * query rows for the nextmatch widget
	 *
	 * @param array $query with keys 'start', 'search', 'order', 'sort', 'col_filter'
	 *	For other keys like 'filter', 'cat_id' you have to reimplement this method in a derived class.
	 * @param array &$rows returned rows/competitions
	 * @param array &$readonlys eg. to disable buttons based on acl, not use here, maybe in a derived class
	 * @param string $join='' sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or
	 *	"LEFT JOIN table2 ON (x=y)", Note: there's no quoting done on $join!
	 * @param boolean $need_full_no_count=false If true an unlimited query is run to determine the total number of rows, default false
	 * @return int total number of rows
	 * optional not used here: $join='',$need_full_no_count=false
	 */
	function get_rows($query,&$rows,&$readonlys)
	{
		// save the nextmatch entrys/settings with the sessiondata
		$GLOBALS['egw']->session->appsession(@$this->called_by.'session_data','test',$query);
    	        return parent::get_rows($query,$rows,$readonlys);
         }
}

---


The outcome of Day 6 as ZiP File


the day before | Up | the day after
You are here