More custom field UI work

- Application sub-type options
- Tracker & Infolog now use common UI
This commit is contained in:
Nathan Gray 2014-10-23 17:33:03 +00:00
parent 1d3769939b
commit bcbf679f64
7 changed files with 212 additions and 310 deletions

View File

@ -2,32 +2,36 @@
<!-- $Id$ -->
<overlay>
<template id="addressbook.admin.types" template="" lang="" group="0" version="">
<grid>
<columns>
<column/>
<column/>
<column/>
</columns>
<rows>
<row>
<hbox span="all">
<description value="Options for type " class="header"/>
<description id="type" no_lang="1" font_style="b"/>
<description/>
</hbox>
</row>
<row>
<description value="Icon"/>
<textbox id="icon" size="40"/>
<description value="Choose an icon for this contact type"/>
</row>
<row>
<description value="Template"/>
<textbox id="template" size="40"/>
<description value="Chosse an eTemplate for this contact type"/>
</row>
</rows>
</grid>
<vbox>
<template id="admin.customfields.types" content="content_types"/>
<grid id="content_type_options">
<columns>
<column/>
<column/>
<column/>
</columns>
<rows>
<row>
<hbox span="all">
<description value="Options for type " class="header"/>
<description id="type" no_lang="1" font_style="b"/>
<description/>
</hbox>
</row>
<row>
<description value="Icon"/>
<textbox id="icon" size="40"/>
<description value="Choose an icon for this contact type"/>
</row>
<row>
<description value="Template"/>
<textbox id="template" size="40"/>
<description value="Chosse an eTemplate for this contact type"/>
</row>
</rows>
</grid>
<button label="Save" id="save"/>
</vbox>
<styles>.header{
font-weight: bold;
}</styles>

View File

@ -11,10 +11,19 @@
*/
/**
* Custiomfields class - manages customfield definitions in egw_config table
*
* The repository name (config_name) is 'customfields'.
*/
* Customfields class - manages customfield definitions in egw_config table
*
* The repository name (config_name) is 'customfields'.
*
* Applications can have customfields by sub-type by having a template
* named '<appname>.admin.types'. See admin.customfields.types as an
* example, but the template can even be empty if types are handled by the
* application in another way.
*
* Applications can extend this class to customize the custom fields and handle
* extra information from the above template by extending and implementing
* update() and app_index().
*/
class customfields
{
@ -38,6 +47,12 @@ class customfields
var $types2 = array();
var $content_types,$fields;
/**
* Currently selected content type (if used by app)
* @var string
*/
protected $content_type = null;
var $public_functions = array(
'index' => true,
'edit' => True
@ -70,7 +85,7 @@ class customfields
public function index($content = array())
{
// determine appname
$this->appname = $_GET['appname'] ? $_GET['appname'] : ($content['appname'] ? $content['appname'] : false);
$this->appname = $this->appname ? $this->appname : ($_GET['appname'] ? $_GET['appname'] : ($content['appname'] ? $content['appname'] : false));
if(!$this->appname) die(lang('Error! No appname found'));
$this->use_private = !isset($_GET['use_private']) || (boolean)$_GET['use_private'] || $content['use_private'];
@ -85,22 +100,39 @@ class customfields
$test = new etemplate_new();
if($test->read($this->appname.'.admin.types')) $this->manage_content_types = true;
// Handle incoming
// Handle incoming - types, options, etc.
if($this->manage_content_types)
{
$this->content_types = config::get_content_types($this->appname);
if(count($this->content_types) == 0)
{
$this->content_types = config::get_content_types($this->appname);
}
if (count($this->content_types)==0)
{
// if you define your default types of your app with the search_link hook, they are available here, if no types were found
$this->content_types = (array)egw_link::get_registry($this->appname,'default_types');
}
if($content['content_types']['delete']) $this->delete_content_type($content);
// Set this now, we need to know it for updates
$this->content_type = $content['content_types']['types'];
// Common type changes - add, delete
if($content['content_types']['delete'])
{
$this->delete_content_type($content);
}
elseif($content['content_types']['create'])
{
$content['content_types']['types'] = $this->create_content_type($content);
unset($content['content_types']['name']);
}
// No common type change and type didn't change, try an update
elseif($this->content_type && is_array($content) && $this->content_type == $content['old_content_type'])
{
$this->update($content);
}
}
// Custom field deleted from nextmatch
if($content['nm']['action'] == 'delete')
{
foreach($this->fields as $name => $data)
@ -137,16 +169,22 @@ class customfields
{
foreach($this->content_types as $type => $entry)
{
if(!is_array($entry))
{
$this->content_types[$type] = array('name' => $entry);
$entry = $this->content_types[$type];
}
$this->types2[$type] = $entry['name'];
}
$sel_options['types'] = $sel_options['cf_type2'] = $this->types2;
$content['type_template'] = $this->appname . '.admin.types';
$content['content_types']['appname'] = $this->appname;
$content_types = array_keys($this->content_types);
$this->content_type = $content['content_types']['types'] ? $content['content_types']['types'] : $content_types[0];
$content['content_type_options'] = $this->content_types[$this->content_type]['options'];
$content['content_type_options']['type'] = $this->content_types[$this->content_type]['name'];
$content['content_type_options']['type'] = $this->types2[$this->content_type];
if ($this->content_types[$this->content_type]['non_deletable'])
{
$content['content_types']['non_deletable'] = true;
@ -173,13 +211,22 @@ class customfields
// Disable content types
$this->tmpl->disableElement('content_types', true);
}
$GLOBALS['egw_info']['flags']['app_header'] = $GLOBALS['egw_info']['apps'][$this->appname]['title'].' - '.lang('Custom fields');
$this->tmpl->exec('admin.customfields.index',$content,$sel_options,$readonlys,array(
$preserve = array(
'appname' => $this->appname,
'use_private' => $this->use_private,
));
'old_content_type' => $this->content_type
);
// Allow extending app a change to change content before display
static::app_index($content, $sel_options, $readonlys, $preserve);
$GLOBALS['egw_info']['flags']['app_header'] = $GLOBALS['egw_info']['apps'][$this->appname]['title'].' - '.lang('Custom fields');
// Some logic to make sure extending class (if there is one) gets called
// when etemplate2 comes back instead of parent class
$exec = get_class() == get_called_class() ? 'admin.customfields.index' : $this->appname . '.' . get_called_class() . '.index';
$this->tmpl->exec($exec,$content,$sel_options,$readonlys,$preserve);
}
/**
@ -193,7 +240,7 @@ class customfields
$cf_id = $_GET['cf_id'] ? (int)$_GET['cf_id'] : (int)$content['cf_id'];
// determine appname
$this->appname = $_GET['appname'] ? $_GET['appname'] : ($content['cf_app'] ? $content['cf_app'] : false);
$this->appname = $this->appname ? $this->appname : ($_GET['appname'] ? $_GET['appname'] : ($content['cf_app'] ? $content['cf_app'] : false));
if(!$this->appname)
{
if($cf_id && $this->so)
@ -295,7 +342,10 @@ class customfields
// Show sub-type row, and get types
if($this->manage_content_types)
{
$this->content_types = config::get_content_types($this->appname);
if(count($this->content_types) == 0)
{
$this->content_types = config::get_content_types($this->appname);
}
if (count($this->content_types)==0)
{
// if you define your default types of your app with the search_link hook, they are available here, if no types were found
@ -303,9 +353,9 @@ class customfields
}
foreach($this->content_types as $type => $entry)
{
$this->types2[$type] = $entry['name'];
$this->types2[$type] = is_array($entry) ? $entry['name'] : $entry;
}
$sel_options['cf_type2'] = $this->types2 + array('tmpl' => 'template');
$sel_options['cf_type2'] = $this->types2;
}
else
{
@ -319,6 +369,15 @@ class customfields
));
}
/**
* Allow extending apps a change to interfere and add content to support
* their custom template. This is called right before etemplate->exec().
*/
protected function app_index(&$content, &$sel_options, &$readonlys)
{
// This is just a stub.
}
/**
* Get actions / context menu for index
*
@ -326,7 +385,7 @@ class customfields
*
* @return array see nextmatch_widget::egw_actions()
*/
private function get_actions()
protected function get_actions()
{
$actions = array(
'open' => array( // does edit if allowed, otherwise view
@ -429,7 +488,6 @@ class customfields
function update(&$content)
{
$this->update_fields($content);
$this->content_types[$this->content_type]['options'] = $content['content_type_options'];
// save changes to repository
$this->save_repository();

View File

@ -91,7 +91,7 @@
</columns>
<rows>
<row>
<template id="admin.customfields.types" content="content_types" span="all"/>
<template id="@type_template" span="all"/>
</row>
<row>
<nextmatch id="nm" template="admin.customfields.fields" header_row="admin.customfields.header_add" span="all"/>

View File

@ -10,26 +10,19 @@
* @version $Id$
*/
require_once(EGW_INCLUDE_ROOT . '/admin/inc/class.customfields.inc.php');
/**
* Administration of custom fields, type and status
*/
class infolog_customfields
class infolog_customfields extends customfields
{
var $public_functions = array(
'edit' => True,
);
public $appname = 'infolog';
/**
* Instance of the infolog BO class
*
* @var infolog_bo
*/
var $bo;
/**
* Instance of the etemplate class
*
* @var etemplate
*/
var $tmpl;
/**
* instance of the config class for infolog
*
@ -45,123 +38,42 @@ class infolog_customfields
function __construct( )
{
parent::__construct('infolog');
$this->bo = new infolog_bo();
$this->tmpl = new etemplate_new();
$this->types = &$this->bo->enums['type'];
$this->content_types = &$this->bo->enums['type'];
$this->status = &$this->bo->status;
$this->config_data = config::read('infolog');
$this->fields = &$this->bo->customfields;
$this->group_owners =& $this->bo->group_owners;
}
/**
* Edit/Create an InfoLog Custom fields, typ and status
*
* @param array $content Content from the eTemplate Exec
*/
function edit($content=null)
{
$GLOBALS['egw_info']['flags']['app_header'] = lang('InfoLog').' - '.lang('Custom fields, typ and status');
if (is_array($content))
{
//echo '<pre style="text-align: left;">'; print_r($content); echo "</pre>\n";
list($action) = @each($content['button']);
switch($action)
{
case 'create':
$this->create($content);
break;
case 'delete':
$this->delete($content);
break;
default:
if (!$content['status']['create'] && !$content['status']['delete'] &&
!$content['fields']['create'] && !$content['fields']['delete'])
{
break; // typ change
}
case 'save':
case 'apply':
$this->update($content);
if ($action != 'save')
{
break;
}
// fall through
case 'cancel':
egw::redirect_link('/index.php', array(
'menuaction' => 'admin.admin_ui.index',
'ajax' => 'true'
), 'admin');
}
}
else
{
list($typ) = each($this->types);
$content = array(
'type2' => $typ,
);
}
$readonlys = array();
$readonlys['button[delete]'] = isset($this->bo->stock_enums['type'][$content['type2']]);
$content['status'] = array(
'default' => $this->status['defaults'][$content['type2']]
);
/**
* Hook from parent class so we can interfere and add owner & status
*/
protected function app_index(&$content, &$sel_options, &$readonlys, &$preserve)
{
$n = 0;
foreach($this->status[$content['type2']] as $name => $label)
foreach($this->status[$this->content_type] as $name => $label)
{
$content['status'][++$n] = array(
$content['content_type_options']['status'][++$n] = array(
'name' => $name,
'label' => $label,
'disabled' => False
);
$preserv_status[$n]['old_name'] = $name;
if (isset($this->bo->stock_status[$content['type2']][$name]))
$preserve['content_type_options']['status'][$n]['old_name'] = $name;
if (isset($this->bo->stock_status[$this->content_type][$name]))
{
$readonlys['status']["delete[$name]"] =
$readonlys['status'][$n.'[name]'] = True;
$readonlys['content_type_options']['status']["delete[$name]"] =
$readonlys['content_type_options']['status'][$n.'[name]'] = True;
}
$readonlys['status']["create$name"] = True;
$readonlys['content_type_options']['status']["create$name"] = True;
}
$content['status'][++$n] = array('name'=>''); // new line for create
$readonlys['status']["delete[]"] = True;
//echo 'customfields=<pre style="text-align: left;">'; print_r($this->fields); echo "</pre>\n";
$content['fields'] = array();
$n = 0;
foreach($this->fields as $name => $field)
{
if (is_array($field['values']))
{
$values = '';
foreach($field['values'] as $var => $value)
{
$values .= (!empty($values) ? "\n" : '').$var.'='.$value;
}
$field['values'] = $values;
}
if (!$field['type2']) $field['type2']=''; // multiselect returns NULL for nothing selected (=All) which stops the autorepeat
$content['fields'][++$n] = $field + array(
'name' => $name
);
$preserv_fields[$n]['old_name'] = $name;
$readonlys['fields']["create$name"] = True;
}
$content['fields'][++$n] = array('type2'=>'','order' => 10 * $n); // new line for create
$readonlys['fields']["delete[]"] = True;
$content['group_owner'] = $this->group_owners[$content['type2']];
//echo '<p>'.__METHOD__'.(content = <pre style="text-align: left;">'; print_r($content); echo "</pre>\n";
//echo 'readonlys = <pre style="text-align: left;">'; print_r($readonlys); echo "</pre>\n";
$this->tmpl->read('infolog.customfields');
$this->tmpl->exec('infolog.infolog_customfields.edit',$content,array(
'type2' => $this->types,
),$readonlys,array(
'status' => $preserv_status,
'fields' => $preserv_fields,
));
$content['content_type_options']['status']['default'] = $this->status['defaults'][$this->content_type];
$content['content_type_options']['group_owner'] = $this->group_owners[$this->content_type];
$readonlys['content_types']['delete'] = isset($this->bo->stock_enums['type'][$this->content_type]);
}
function update_fields(&$content)
@ -247,8 +159,8 @@ class infolog_customfields
function update_status(&$content)
{
$typ = $content['type2'];
$status = &$content['status'];
$typ = $this->content_type;
$status = &$content['content_type_options']['status'];
$default = $status['default'];
unset($status['default']);
@ -308,15 +220,14 @@ class infolog_customfields
function update(&$content)
{
$this->update_status($content);
$this->update_fields($content);
if ($content['group_owner'])
if ($content['content_type_options']['group_owner'])
{
$this->group_owners[$content['type2']] = $content['group_owner'];
$this->group_owners[$this->content_type] = $content['content_type_options']['group_owner'];
}
else
{
unset($this->group_owners[$content['type2']]);
unset($this->group_owners[$this->content_type]);
}
// save changes to repository
$this->save_repository();
@ -329,11 +240,11 @@ class infolog_customfields
$content['error_msg'] .= lang("You can't delete one of the stock types !!!");
return;
}
unset($this->types[$content['type2']]);
unset($this->content_types[$content['type2']]);
unset($this->status[$content['type2']]);
unset($this->status['defaults'][$content['type2']]);
unset($this->group_owners[$content['type2']]);
list($content['type2']) = each($this->types);
list($content['type2']) = each($this->content_types);
// save changes to repository
$this->save_repository();
@ -343,7 +254,7 @@ class infolog_customfields
{
$new_name = trim($content['new_name']);
unset($content['new_name']);
if (empty($new_name) || isset($this->types[$new_name]))
if (empty($new_name) || isset($this->content_types[$new_name]))
{
$content['error_msg'] .= empty($new_name) ?
lang('You have to enter a name, to create a new typ!!!') :
@ -351,7 +262,7 @@ class infolog_customfields
}
else
{
$this->types[$new_name] = $new_name;
$this->content_types[$new_name] = $new_name;
$this->status[$new_name] = array(
'ongoing' => 'ongoing',
'done' => 'done'
@ -367,7 +278,7 @@ class infolog_customfields
function save_repository()
{
// save changes to repository
config::save_value('types',$this->types,'infolog');
config::save_value('types',$this->content_types,'infolog');
//echo '<p>'.__METHOD__.'() \$this->status=<pre style="text-aling: left;">'; print_r($this->status); echo "</pre>\n";
config::save_value('status',$this->status,'infolog');
//echo '<p>'.__METHOD__.'() \$this->fields=<pre style="text-aling: left;">'; print_r($this->fields); echo "</pre>\n";

View File

@ -115,7 +115,7 @@ class infolog_hooks
'appname' => $appname,
'global_cats'=> True)),
'Custom fields, typ and status' => egw::link('/index.php',array(
'menuaction' => 'infolog.infolog_customfields.edit'), 'admin'),
'menuaction' => 'infolog.infolog_customfields.index'), 'admin'),
);
if ($location == 'admin')
{

View File

@ -0,0 +1,71 @@
<?xml version="1.0"?>
<!-- $Id$ -->
<overlay>
<template id="infolog.customfields.status" template="" lang="" group="0" version="1.2.001">
<grid>
<columns>
<column/>
<column/>
<column/>
<column/>
<column disabled="1"/>
<column/>
</columns>
<rows>
<row class="th">
<description value="Name"/>
<description value="Label"/>
<description value="Translation"/>
<description value="Default"/>
<description value="Disabled"/>
<description align="center" value="Action"/>
</row>
<row class="row">
<textbox statustext="the name used internaly (&lt;= 10 chars), changeing it makes existing data unavailible" id="${row}[name]" size="10" maxlength="40"/>
<textbox statustext="the text displayed to the user" id="${row}[label]" size="40"/>
<description id="${row}[label]"/>
<radio align="center" statustext="default status for a new log entry" id="default" options="$row_cont[name]"/>
<checkbox align="center" statustext="disables a status without deleting it" id="${row}[disabled]"/>
<hbox>
<button statustext="deletes this status" label="Delete" id="delete[$row_cont[name]]"/>
<button statustext="creates a new status with the given values" label="Create" id="create$row_cont[name]"/>
</hbox>
</row>
</rows>
</grid>
</template>
<template id="infolog.admin.types" template="" lang="" group="0" version="">
<vbox>
<template id="admin.customfields.types" content="content_types"/>
<grid id="content_type_options">
<columns>
<column/>
<column/>
<column/>
</columns>
<rows>
<row>
<hbox span="all">
<description value="Group owner for" class="header"/>
<description id="type" class="header"/>
<menulist class="infolog_lpadding5">
<menupopup type="select-account" statustext="If a type has a group owner, all entries of that type will be owned by the given group and NOT the user who created it!" id="group_owner" options="None,groups"/>
</menulist>
</hbox>
</row>
<row class="header">
<description value="Custom status for typ" id="typ" span="all"/>
</row>
<row>
<template id="infolog.customfields.status" content="status" span="all"/>
</row>
</rows>
</grid>
<button label="Save" id="save"/>
<description value="Custom fields" span="all" class="header"/>
</vbox>
<styles>.header{
font-weight: bold;
}</styles>
</template>
</overlay>

View File

@ -1,142 +0,0 @@
<?xml version="1.0"?>
<!-- $Id$ -->
<overlay>
<template id="infolog.customfields.status" template="" lang="" group="0" version="1.2.001">
<grid>
<columns>
<column/>
<column/>
<column/>
<column/>
<column disabled="1"/>
<column/>
</columns>
<rows>
<row class="th">
<description value="Name"/>
<description value="Label"/>
<description value="Translation"/>
<description value="Default"/>
<description value="Disabled"/>
<description align="center" value="Action"/>
</row>
<row class="row">
<textbox statustext="the name used internaly (&lt;= 10 chars), changeing it makes existing data unavailible" id="${row}[name]" size="10" maxlength="40"/>
<textbox statustext="the text displayed to the user" id="${row}[label]" size="40"/>
<description id="${row}[label]"/>
<radio align="center" statustext="default status for a new log entry" id="default" options="$row_cont[name]"/>
<checkbox align="center" statustext="disables a status without deleting it" id="${row}[disabled]"/>
<hbox>
<button statustext="deletes this status" label="Delete" id="delete[$row_cont[name]]"/>
<button statustext="creates a new status with the given values" label="Create" id="create$row_cont[name]"/>
</hbox>
</row>
</rows>
</grid>
</template>
<template id="infolog.customfields.fields" template="" lang="" group="0" version="1.9.001">
<grid>
<columns>
<column/>
<column/>
<column/>
<column/>
<column/>
<column/>
<column/>
<column/>
</columns>
<rows>
<row class="th">
<description value="Type"/>
<description value="Name"/>
<description value="Label"/>
<description value="Type of field"/>
<description value="Values for selectbox"/>
<vbox>
<description value="Length"/>
<description value="Rows"/>
</vbox>
<description value="Order"/>
<description align="center" statustext="deletes this field" value="Action"/>
</row>
<row class="row" valign="top">
<listbox statustext="for which types should this field be used" id="${row}[type2]" no_lang="1" rows="3"/>
<textbox statustext="the name used internaly (&lt;= 20 chars), changeing it makes existing data unavailible" id="${row}[name]" size="20" maxlength="32"/>
<vbox>
<textbox statustext="the text displayed to the user" id="${row}[label]" maxlength="255"/>
<description id="${row}[label]"/>
</vbox>
<vbox>
<customfields-types statustext="Type of customfield" id="{$row}[type]"/>
<checkbox label="required" id="${row}[needed]"/>
</vbox>
<textbox multiline="true" statustext="each value is a line like &lt;id&gt;[=&lt;label&gt;]" id="${row}[values]" rows="4" cols="30"/>
<vbox>
<textbox statustext="max length of the input [, length of the inputfield (optional)]" id="${row}[len]" size="5"/>
<textbox type="integer" statustext="number of row for a multiline inputfield or line of a multi-select-box" id="${row}[rows]" min="0" max="10" size="2"/>
</vbox>
<textbox type="integer" statustext="determines the order the fields are displayed" id="${row}[order]" min="1" size="3"/>
<hbox>
<button statustext="deletes this field" label="Delete" id="delete[$row_cont[name]]"/>
<button statustext="creates a new field" label="Create" id="create$row_cont[name]"/>
</hbox>
</row>
</rows>
</grid>
</template>
<template id="infolog.customfields" template="" lang="" group="0" version="1.3.002">
<grid>
<columns>
<column/>
<column/>
<column/>
<column/>
<column/>
<column width="80%"/>
</columns>
<rows>
<row>
<description value="Typ"/>
<menulist>
<menupopup statustext="select a typ to edit it's status-values or delete it" id="type2" no_lang="1" onchange="1"/>
</menulist>
<button statustext="deletes the selected typ" label="Delete" id="button[delete]"/>
<textbox blur="new name" statustext="name of new type to create" id="new_name" size="10" maxlength="40"/>
<button statustext="creates a new typ with the given name" label="Create" id="button[create]"/>
<description align="center" id="error_msg" no_lang="1" class="msg"/>
</row>
<row class="header">
<description value="Custom status for typ" id="typ" span="all"/>
</row>
<row>
<template id="status" content="status" span="all"/>
</row>
<row class="header">
<description value="Custom fields" span="all"/>
</row>
<row>
<template id="fields" content="fields" span="all"/>
</row>
<row>
<hbox span="all">
<description value="Group owner for" id="type2" class="header"/>
<menulist class="infolog_lpadding5">
<menupopup type="select-account" statustext="If a type has a group owner, all entries of that type will be owned by the given group and NOT the user who created it!" id="group_owner" options="None,groups"/>
</menulist>
</hbox>
</row>
<row>
<hbox span="all">
<button statustext="saves the changes made and leaves" label="Save" id="button[save]"/>
<button statustext="apply the changes" label="Apply" id="button[apply]"/>
<button statustext="leaves without saveing" label="Cancel" id="button[cancel]"/>
</hbox>
</row>
</rows>
</grid>
<styles>
.header { font-weight: bold; font-size: 120%; }
</styles>
</template>
</overlay>