community.egroupware.org: Community wiki

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

After creating an action menu over our NextMatch widget and getting more familiar with actions, it is time to make our application a bit more complex and nicer by using some other features eg. popup, links, attach files, categories, and .

DO NOT WORRY! We will implement them one by one, step-by-step.

As you already have noticed, currently we are able to create, display, and edit our selected record on a grid on the top left corner of our test.index etemplate with our NextMatch widget on the same page. It would be nice if we can handle these actions for a selected record on a popup window (See figure 7.0), and we can simply disable the grid that includes the firstname, last name, and the buttons in the left corner.

:: (figure 7.0)

So, let's start to call our popup window. As first step, we need to initiate our actions' parameters located in the get_actions() method, in order to define from which actions (eg. open, edit, ...), we want to call a popup window. It would be good if we add another action in our actions menu like open.
---
$actions = array(
			'open' => array( //setting 'open' array's parameters 
				'caption'  => 'open',
				'default'  => true,
				'group' => 1,
				'url' => 'menuaction=test.test_ui.edit&test_id=$id',
				'popup' => '600x400',//egw_link::get_registry('test', 'view'),
				),
			'edit' => array(
				'caption' => 'edit',
				'default' => true,
				'allowOnMultiple' => false, // it does not allow to multiple selected popup
				'url' => 'menuaction=test.test_ui.edit&test_id=$id',
				'popup' => '600x400',
				'group'  => 1,
				
			),
			'delete' => array(	
				'caption' => 'delete',
				'group'  => 1,
				
			),
		);
---

As you see in the code, we set for both open and edit actions, the 'popup' => 400x600, which actives a popup window with size 600x400, and 'url' => 'menuaction=test.test_ui.edit&test_id=$id' , which sets the url of our popup window with menuaction of test.test_ui.edit, and our test_id.
Now we need to define our edit method in the test_ui class.
--- 
function edit ($content = null)
{
      $tmpl_edit = new etemplate('test.edit');
      $tmpl_edit->exec('test.test_ui.edit',$content,$sel_options,'',array('test_id'=>$this->data['test_id']),2); // '2' as out_mode param which is echo without navbar 
}
---

and set it in the $public_function array as well.
---
var $public_functions = array(
		'testinterface'	=> True,
		'index'	=> True,
		'edit' 	=> True,// calling our edit function
		);
---

If we run the application, by clicking on each of the edit or open (See Figure 7.1) actions on the menu action, we will get a empty popup window with test_id at its address bar (See Figure 7.2).


(Figure 7.1)


(Figure 7.2)

So far, we have not designed our new etemplate (test.edit), we just have created and defined a method for it. Thus, we go back to our etemplate module to design test.edit template like this (See Figure 7.3):


(Figure 7.3)

As you see on the figure 7.3, we have added an ID, first name and last name, and set their name field to test_id, test_firstname, and test_name perspectively. Also, we have added three submit buttons Save, Cancel and Delete. Now its the time to define some codes for these actions in our edit method too.

---
function edit ($content = null)
{
	$tmpl_edit = new etemplate('test.edit');//
	if (!is_array($content))
	{
		if ((int)$_GET['test_id'])
		{   
			//Getting test_id from the url and reading the content 
			$this->read((int)$_GET['test_id']); //
			
		}
			
	}
        else
	{
               	//actions on test.edit etemplate
		//Fetching the button, to a list	
		list($button) = @each($content['button']); 
		$this->data = $content; // To save our data on the edit form when to apply 
		switch ($button) 
		{	
			//Save action. 
			case 'save': 
				$content['datetime']=time();
				$this->data['test_dts']=$this->now;
				$this->save();
			        common::egw_exit();
				break;
			//Delete action.
			case 'delete':	
				$del = $content['test_id'];
   				$this->delete(array('test_id'=>$del));
				common::egw_exit();
				break;
			//Cancel action.
			case 'cancel':
				common::egw_exit();
				break;
		}
	}
		
	$tmpl_edit->exec('test.test_ui.edit',$content,$sel_options,'',array('test_id'=>$this->data['test_id']),2); // '2' as out_mode param which is echo without navbar
}	
---

If we look at the edit method precisely, we can see the test_id posted to our popup window is read by $_Get['test_id'] and then we set the fields by $this->read((int)$_Get['test_id']);. Also, in order to proceed each action of the submit buttons, it is recommended to define the submit button's name like, button[{button's name}] eg. button[delete] , then easily we can verify the selected button inside the code with a switch.

Now if we run the application and open a selected record, we will see all information from the selected record on popup window's field we have designed. But it seems there is something missing here, because when we click on each of buttons on the popup window, the window is closed but our NextMatch widget doesn't show the updates!?
In order to referesh the NextMatch widget, we are going to call egw_referesh in the edit method where we need to get refresh our NextMatch widget after an action.

---
//Refreshing the page
$js = "opener.egw_refresh('".str_replace("'","\\'",lang('Contact Saved'))."','test',{$content['test_id']},'save');";
$js.= "if(opener.egw_getAppName() != 'test')";
$js.="{opener.egw_refresh('".str_replace("'","\\'",lang('Contact saved'))."','test',{$content['test_id']},null,'test');}";
$js.= "window.close();";
---

and then loading the $js inside the html, like this:



So far we managed to get the popup window and designed our test.edit etemplate to display and modify our records. The next step would be using some other useful widgets like links and attachments, customfields, categories, and ACL.

Lets start with links and attachments.

First of all, like the other widget we need to go to etemplate module and then read our test.edit etemplate in order to modify it again. Lets put a grid between the our record information (firstname, lastname, and property) and the buttons. Since we would to add lots of widget and features on our popup, it would be good to organise them into tabs by selecting Tabs widget (see Figure 7.4).



(Figure 7.4)

To define our tabs, we can say in the label field of the Tabs widget, {tab1}|{tab2}| {...} eg. links|customfields, and in the name field, test.edit.links|test.edit.customfields which later on we will design these new etemplate extensions as test.edit.links and test.edit.customfields.

Let's design our test.edit.links etemplate by adding another widget, called link to on it, which stores links and attachments for our records. (see Figure 7.5)


(Figure 7.5)

Now we need to assign the test_id to the id of the links by adding this codes inside the edit method:

---
 // Adding the link id in order to getting existing links
$content = array_merge($this->data,array( 
'tabs' => $content['tabs'],
'link_to' => array(
'to_id' => $this->data['test_id'] ? $this->data['test_id'] : $content['link_to']['to_id'],
'to_app' => 'test',
)));
---

Run the test application again and we see that a utilise fancy tabs appeared on the popup window and under the links tab, we have options to search links and attachment files. After testing the App., you will realise that when we add new record, which still has no test_id in the database, the links are not able to assign to our record for the first time. To solve this problem, we would add couple of things:

1- Add another button widget on the test.edit and call it Add. Then choose the onClick event of the button as a popup, and set its value to menuaction=test.test_ui.edit,,,400 (see Figure 7.6).



(Figure 7.6)

2- Add this condition code with calling the link method inside the edit method:

---
if (is_array($content['link_to']['to_id']) && count($content['link_to']['to_id']))
{
	egw_link::link('test',$this->data['test_id'],$content['link_to']['to_id']);
}
---

So, by now we should be able to create new records, edit, delete, and store links and attachments through our nice popup window. WE DID A GOOD JOB!

The outcome of Day 7 - Part 1 as ZiP File

The Day 7th Part 2:



Now we are ready to go one step further and define the Customfields . We have already took our first step by adding the customfields on the Tabs widget, but there are couple of more things that we need to know about.

Hooks: The hooks are

In order to define any sort of settings, preferences, right access or other app. settings, we need to define them as hooks. Simply we are going to create a new file on /inc directory and call it class.test_hooks.inc.php and then we can define our hook class on it.

class.test_hooks.inc.php

---
class test_hooks
{
        static function all_hooks($args)
	{
		$appname = 'test';
		$location = is_array($args) ? $args['location'] : $args;
		
		if ($location == 'sidebox_menu')
		{
			$file = array(
			);
			display_sidebox($appname,$GLOBALS['egw_info']['apps'][$appname]['title'].' '.lang('Menu'),$file);
		}
                 if ($GLOBALS['egw_info']['user']['apps']['admin'] && $location != 'preferences')
		{
			$file = Array(
				
				'Custom fields' => egw::link('/index.php','menuaction=admin.customfields.edit&appname='.$appname.'&use_private=1'),
				);
			if ($location == 'admin')
			{
				display_section($appname,$file);
			}
			else
			{
				display_sidebox($appname,lang('Admin'),$file);
			}
		}
         }
}         

---


If we run the application, we should not get any error and also we will not get any sidebox menu on the left side of our application. It is because that the hooks are not registered yet. How do we register hooks? Good question. We need to go to the /setup/setup.inc.php and add the following line to our setup file.

setup.inc.php

---
//The application's hooks rergistered.
$setup_info['test']['hooks']['admin'] = 'test_hooks::all_hooks';
$setup_info['test']['hooks']['sidebox_menu'] = 'test_hooks::all_hooks';
---

Now we should get a nice sidebox menu like Figure 7.7.

**NOTE: Since we have registered the hooks on setup.inc.php, we need to re-setup the application through the EGroupware Setup in order to apply all the changes.

(Figure 7.7)

There is a custom fields menu on the sidebox menu, and if we click on it, it will be redirected to test-Custom Fields page where we can create custom fields. The custom fields are stored for the test application and shown in the custom fields widget in the tab on our popup window, but we still can not store customfields values for each record, because we have not defined a database table to store them. We need to go to etemplate -> DB-tools and creating a new table called egw_test_extra with three fields: test_id as int; test_extra_name as vchar and test_extra_value as vchar, on the DB under the test application (see Figure 7.8).



(Figure 7.8)

So far what we have used to handle the sql queries in the class.test_so.inc.php, is so_sql class. But while we are going to use custom fields in our application, then we need to handle the extra table, too, which the so_sql class can not take care of it. Therefore we need to use another class, called so_sql_cf instead of so_sql.

So, replace this line:

---
include_once(EGW_INCLUDE_ROOT . '/etemplate/inc/class.so_sql.inc.php');
---

with this:

---
include_once(EGW_INCLUDE_ROOT . '/etemplate/inc/class.so_sql_cf.inc.php');
---

in the class.test_so.inc.php file. Also we need to change parent class constructor initialization like this:

---
parent::__construct('test','egw_test',self::EXTRA_TABLE,'','test_extra_name','test_extra_value','test_id');
---

**Note: the variable EXTRA_TABLE needs to be defined in the class:
---
const EXTRA_TABLE = 'egw_test_extra';
---

If we run the application, we would be able to create custom fields through the sidebox menu custom fields (see Figure 7.9), and to store it for each record by filling the defined custom fields (tel in our case) at the custom fields tab on the popup window (see Figure 7.10).


(Figure 7.9)


(Figure 7.10)

As you have noticed, we don't see any custom fields on our NextMatch widget, therefore it needs to be defined by adding a new column before the actions column like this (see Figure 7.11):

(Figure 7.11)

(Figure 7.12)

Now look at the index page again, our custom field, "tel" is appeared. Congrate!!!

(Figure 7.13)

Since we got more familiar with Hooks, we still have just two more things to get the links job done from the first part. 1- as you might be noticed already, when we look at the list of applications on link widget, there is no test application on the list, because what we have done so far, we get other application lists and able to add their links in our application but we have not hooked the test application yet. 2- we need to be able to update and delete (unlink) links. let's see what should we do!

1-Hooking the test application:
First of all we should search for links of our application, which we can do it by defining another method in class.test_hooks.inc.php, which return our link-registry as an array, let's call it search_link.

---
/**
 * Hook called by link-class to include test in the appregistry of the linkage
 *
 * @param array/string $location location and other parameters (not used)
 * @return array with method-names
 */
static function search_link($location)
{
	return array(
		'title' => 'test.test_bo.link_title',
                'query' => 'test.test_bo.link_query',
		'view'  => array(
		           'menuaction' => 'test.test_ui.edit',
		           ),
		'view_id' => 'test_id',
		'view_popup'  => '600x425',
		'edit_popup'  => '600x425',
		'index' => array(
			'menuaction' => 'test.test_ui.index',
			),
		'edit' => array(
			'menuaction' => 'test.test_ui.edit',
		         ),
		'edit_id'    => 'test_id',
		'name'     => 'Test',
		'edit_popup'  => '600x425',
                       
	);
}
---

**Note: Do not forget to call search_link method on setup.inc.php as well by adding this code:

---
$setup_info['test']['hooks']['search_link'] = 'test_hooks::search_link';
---
<code>

As you see in the code, we do have two methods called '''link_title''' and '''link_query''' which are addressed to the '''test_bo''' class, just remind that we need to define these two methods. The link_query in order to search records and the the link_title method to return our '''last_name''' and the '''test_id''' as the link's indicator. 

'''class.test_bo.inc.php

<code>
---
/**
 * get title for a test entry identified by $entry
 *
 * Is called as hook to participate in the linking
 *
 * @param int/array $entry int test_id or array with test entry
 * @return string/boolean string with title, null if test not found, false if no perms to view it
 */
function link_title( $entry )
{
       if (!is_array($entry))
       {
            $entry = $this->read( $entry,false,false);
       }
       if (!$entry)
       {
            return $entry;
       }
       return $entry['test_name'] . '#' . $entry['test_id'];
}

/**
 * query test for entries matching $pattern
 *
 * Is called as hook to participate in the linking
 *
 * @param string $pattern pattern to search
 * @param array $options Array of options for the search
 * @return array with test_id - title pairs of the matching entries
 */
 function link_query( $pattern, Array &$options = array() )
{
        $limit = false;
	$result = array();
	if($options['start'] || $options['num_rows'])
        {
		$limit = array($options['start'], $options['num_rows']);
	}
	foreach((array) $this->search($pattern,false,'test_name ASC','','%',false,'OR',$limit, null, '', TRUE) as $rec )
	{
                if ($rec) $result[$rec['test_id']] = $this->link_title($rec);
	}
	$options['total'] = $this->total;
        return $result;
}
---

The return of link_query method is an array like this :

Array (
[test_id] => link_title()

)

which each record as link will get an id as it's own test_id, then if we click on the added link on the link list, the link open a popup window with the menuaction that we already set on search_link method on hooks class with the id of the link which is assigned by link_query method to the link.

2- Update and Delete (un-link) links:
Update or Delete (un-link) a link happens when we update or delete a linked record from the database. Therefore, we should consider these two actions on the link when we do save or delete, then we can use egw_link::notify_update($app,$id,$data=null) method for the updating and egw_link::unlink($link_id,$app=,$id=,$owner=0,$app2=,$id2=,$hold_for_purge=false) method for the un-linking. It is good to do it, by re-implementation of the delete() and save() method in our test_bo class like this:

class.test_bo.inc.php
---
/**
 * saves a test record entry
 *
 * reimplemented to notify the link-class
 *
 * @param array $keys if given $keys are copied to data before saveing => allows a save as
 * @return int 0 on success and errno != 0 else
 */
function save($keys=null)
{
			
        if (!($err = parent::save()))
	{
		// notify the link-class about the update, as other apps may be subscribt to it
		egw_link::notify_update('test',$this->data['test_id'],$this->data);
	}
	return $err;
}

/**
 * deletes a test record entry identified by $keys or the loaded one, reimplemented to notify the link class (unlink)
 *
 * @param array $keys if given array with col => value pairs to characterise the rows to delete
 * @return int affected rows, should be 1 if ok, 0 if an error
 */
function delete($keys=null)
{
	if (!is_array($keys) && (int) $keys)
	{
		$keys = array('test_id' => (int) $keys);
	}
	$test_id = is_null($keys) ? $this->data['test_id'] : $keys['test_id'];
	if ($ret = parent::delete($keys) && $test_id)
        {
            // delete all links to test entry $test_id
            egw_link::unlink(0,'test',$test_id);
	}
	return $ret;
}
---



The outcome of Day 7 - Part 2 as ZiP File

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