Summary | Add preference data type support for floating point numbers |
Queue | Horde Base |
Queue Version | 3.0.3 |
Type | Enhancement |
State | Rejected |
Priority | 1. Low |
Owners | |
Requester | scott (at) realorganized (dot) com |
Created | 02/17/2005 (7462 days ago) |
Due | |
Updated | 10/23/2005 (7214 days ago) |
Assigned | 02/17/2005 (7462 days ago) |
Resolved | 10/23/2005 (7214 days ago) |
Milestone | |
Patch | No |
State ⇒ Rejected
Taken from
http://wiki.horde.org/AddingPages for wiki info, and please upload the
diffs - this ticket is way too long and old to be comprehensible with
inline changes.
create a new page.
Here is the text for your review:
Preferences are an important infrastructure capability of the Horde
Framework. Each user has their own preference settings. These
settings are preserved from session to session and therefore are a
useful way to retain long-term preference settings for the user.
Since preferences are unique to each user, it would not be appropriate
to use preferences to specify general server settings for the entire
Horde server. You should use the Horde Configuration capability for
that. Preferences are saved across multiple sessions. If you would
like to store state information for a particular session, use the PHP
session capability.
Horde preferences can be specific to your application or can be shared
amongst multiple applications. Generally, your preference settings
would be local and not shared. However, if you had a suite of
applications which each used a particular preference you would
designate that preference as shared.
Horde preferences can be locked. What this means is that the user
would not be able to view, or edit this preference from the setting
screens. However, locked preferences can be changed via programatic
interface. For example, if you allowed users to modify their sort
order via a control within your application, then there would be no
need to display this information in the settings screens. This
preference setting would be considered locked. Another use for a
locked preference is to store a preference for a setting that you have
not yet decided to expose to the user. Your code can operate on this
setting, but the user is not allowed to view or change it.
To initialize your preferences, you would place your initial
preference settings in the prefs.php file located here:
yourapplication/config/prefs.php
Each preference setting is called a property and is initialized with
code similar to that shown below:
$_prefs['yourpropertyname'] = array(
'value' => '28',
'locked' => false,
'shared' => false,
'type' => 'floatnumber',
'desc' => _("% Housing-to-Income Ratio")
);
The 'value' element is the default value for your property. 'locked'
and 'shared' are as we have discussed above. 'type' is the way you
would like your data to be presented to the user when they view your
preferences panel. 'desc' is a description string that is displayed
in your preferences panel describing the property. Notice the double
quotes and enclosure using _( ). This is used for
internationalization support.
If you have many preferences, you can organize them into preference
groups. These are logical groups of one or more properties organized
into individual preference penal. When the user views the settings
for your application, they would see an initial screen of all the
individual preference panels. The user then selects an individual
panel (group).
To create a preference group, you would add code like this to your
prefs.php file:
$prefGroups['calculator'] = array(
'column' => _("Calculator Settings"),
'label' => _("Calculator Defaults"),
'desc' => _("Change default calculator settings."),
'members' => array('yorproperty', 'anotherproperty', ... )
);
Add a section like that shown above for each preference panel.
Information about possible values for each of these arrays can be
found here: 'horde/config/prefs.php'
The predefined types provided are fairly complete and can provide
basic support for user preferences. However, there will always be a
need to create some level of customization of individual properties.
To create your own custom property handler, set your 'type' => 'special'.
Then add a file to
'yourapplication/templates/prefs/yourpropertyname.inc' containng the
html code generation code for the control.
Add a function handler into the file 'yourapplication/lib/prefs.php'
The function handler is of the form 'handle_yourpropertyname($updated)'
One limitation of custom property handlers is that they are specific
to an individual property. If you have several properties that need
the same custom control, you cannot reuse the handler. For cases
where you would like to reuse your handler, you can create your own
custom type hander.
To create your own custom type handler:
Assign your own custom type to 'type' => yourcustomtype
Place a file inside your application at
'templates/prefs/yourcustomtype.inc' which contains the html for the
control.
Inside of the lib/prefs.php in your application, add a function
'handle_yourcustotype'
Warning: If you have a shared preference, custom UI gadgets will not
be available to other applications.
1. Apply the changes described in the comment on 02/17/05 to ui.php.
This change includes both the floating point field as well as the
ability to create new types.
2. Add the attached file floatnumber.inc included and described in the
note on 2/16/2005. You can ignore the change to ui.php in that note
as it is in the 2/17/05 note.
I'll add the Wiki documentation today.
State ⇒ Feedback
And I think the steps necessary to create such custom prefs (as well
as how to use pref in general) should better be documented in the wiki
than in the source code.
application's pref.php file for each type that a UI widget is created
for. The $pref variable is the individual preference for which the UI
widget is being handled for.
function handle_largefloat($updated, $pref)
{
global $prefs, $notification;
$num = Util::getPost($pref);
if (floatval($num) != $num) {
$notification->push(_("This value must be a number."), 'horde.error');
} else {
$updated = $updated | $prefs->setValue($pref, $num);
}
return $updated;
}
The .inc file with the html code is identical to the existing help
controls with the exception that the file name uses the type rather
than the property name.
gadgets for specific properties within your application. While useful,
this has the disadvantage that the UI gadgets are one-of-a-kind and
therefore do not lend themselves to multiple use of the preerences UI
gadget that you create. For example, if I created an RGB control, it
would be great if I could simply define a type 'rgbcolor' in such a
way that I could use this gadget and handler for any properties that I
like. I think this promotes reusability and have a particular need
for this in the application that I am developing as I have several
dozen preference settings with some which are similar to one another.
Instructions for use are in a comment in the code.
Towards this end, I propose the following patch. It requires two
quite small changes to the horde/lib/horde/prefs/ui.php file. I have
marked the changes with SDS.
function generateUI($group = null)
{
global $browser, $conf, $prefs, $prefGroups, $_prefs, $registry, $app;
/* Check if any options are actually available. */
if (is_null($prefGroups)) {
$GLOBALS['notification']->push(_("There are no options
available."), 'horde.message');
}
/* Show the header. */
Prefs_UI::generateHeader($group);
/* Assign variables to hold select lists. */
if (!$prefs->isLocked('language')) {
$GLOBALS['language_options'] = &$GLOBALS['nls']['languages'];
}
if (!empty($group) && Prefs_UI::groupIsEditable($group)) {
foreach ($prefGroups[$group]['members'] as $pref) {
if (!$prefs->isLocked($pref)) {
/* Get the help link. */
if (!empty($_prefs[$pref]['help'])) {
$helplink =
Help::link(!empty($_prefs[$pref]['shared']) ? 'horde' :
$registry->getApp(), $_prefs[$pref]['help']);
} else {
$helplink = null;
}
switch ($_prefs[$pref]['type']) {
case 'implicit':
break;
case 'special':
require $registry->get('templates',
!empty($_prefs[$pref]['shared']) ? 'horde' : $registry->getApp()) .
"/prefs/$pref.inc";
break;
case 'link': // SDS 2/17/2005
case 'select':
case 'text':
case 'textarea':
case 'password':
case 'enum':
case 'multienum':
case 'number':
case 'floatnumber':
case 'checkbox':
require $registry->get('templates', 'horde')
. '/prefs/' . $_prefs[$pref]['type'] . '.inc';
break;
default: // SDS 2/17/2005
/* Individual applications can create their own
class of UI handler
* just invent your own type. Then place a
file inside your application
* at templates/prefs/yourcustomtype.inc
which contains your html for the control.
* Inside of the lib/prefs.php in your
application, add a function
* handle_yourcustotype and you are done. If
you have a shared
* preference, this UI gadget will not be
available to other applications.
* Differs from 'special' in that you can
re-use same UI widget
* in your application's preferences. */
$custType = $_prefs[$pref]['type'];
require $registry->get('templates',
$registry->getApp()) . "/prefs/$custType.inc";
break;
}
}
}
require $registry->get('templates', 'horde') . '/prefs/end.inc';
} else {
$columns = array();
if (is_array($prefGroups)) {
foreach ($prefGroups as $group => $gvals) {
if (Prefs_UI::groupIsEditable($group)) {
$col = $gvals['column'];
unset($gvals['column']);
$columns[$col][$group] = $gvals;
}
}
$span = round(100 / count($columns));
} else {
$span = 100;
}
require $registry->get('templates', 'horde') .
'/prefs/overview.inc';
}
}
function handleForm(&$group, &$save)
{
global $prefs, $prefGroups, $_prefs, $notification, $registry;
$updated = false;
/* Run through the action handlers */
if (Util::getPost('actionID') == 'update_prefs') {
if (isset($group) && Prefs_UI::groupIsEditable($group)) {
$updated = false;
foreach ($prefGroups[$group]['members'] as $pref) {
if (!$prefs->isLocked($pref) ||
($_prefs[$pref]['type'] == 'special')) {
switch ($_prefs[$pref]['type']) {
/* These either aren't set or are set in other
* parts of the UI. */
case 'implicit':
case 'link':
break;
case 'select':
case 'text':
case 'textarea':
case 'password':
$updated = $updated |
$save->setValue($pref, Util::getPost($pref));
break;
case 'enum':
$val = Util::getPost($pref);
if (isset($_prefs[$pref]['enum'][$val])) {
$updated = $updated |
$save->setValue($pref, $val);
} else {
$notification->push(_("An illegal
value was specified."), 'horde.error');
}
break;
case 'multienum':
$vals = Util::getPost($pref);
$set = array();
$invalid = false;
if (is_array($vals)) {
foreach ($vals as $val) {
if (isset($_prefs[$pref]['enum'][$val])) {
$set[] = $val;
} else {
$invalid = true;
continue;
}
}
}
if ($invalid) {
$notification->push(_("An illegal
value was specified."), 'horde.error');
} else {
$updated = $updated |
$save->setValue($pref, @serialize($set));
}
break;
case 'number':
$num = Util::getPost($pref);
if (intval($num) != $num) {
$notification->push(_("This value
must be a number."), 'horde.error');
} elseif ($num == 0) {
$notification->push(_("This number
must be at least one."), 'horde.error');
} else {
$updated = $updated |
$save->setValue($pref, $num);
}
break;
case 'floatnumber': // SDS 2/16/2005
$num = Util::getPost($pref);
if (floatval($num) != $num) {
$notification->push(_("This value
must be a number."), 'horde.error');
} else {
$updated = $updated |
$save->setValue($pref, $num);
}
break;
case 'checkbox':
$val = Util::getPost($pref);
$updated = $updated |
$save->setValue($pref, isset($val) ? 1 : 0);
break;
case 'special':
/* Code for special elements must be
* written specifically for each
* application. */
if (function_exists('handle_' . $pref)) {
$updated = $updated |
call_user_func('handle_' . $pref, $updated);
}
break;
default : // SDS 2/17/2004
/* Individual applications can create
their own class of UI handler
* just use the special 'type' that you create
* differs from 'special' in that you can
use same UI widget
* in several places in your
application's preferences . */
if (function_exists('handle_' .
$_prefs[$pref]['type'])) {
$updated = $updated |
call_user_func('handle_' . $_prefs[$pref]['type'], $updated, $pref);
}
break;
}
}
}
if ($updated) {
if (function_exists('prefs_callback')) {
prefs_callback();
}
$notification->push(_("Your options have been
updated."), 'horde.message');
$group = null;
}
}
}
return $updated;
}
/**
* Generate the UI for the preferences interface, either for a
* specific group, or the group selection interface.
*
* @access public
*
* @param optional string $group The group to generate the UI for.
*/
function generateUI($group = null)
{
global $browser, $conf, $prefs, $prefGroups, $_prefs, $registry, $app;
/* Check if any options are actually available. */
if (is_null($prefGroups)) {
$GLOBALS['notification']->push(_("There are no options
available."), 'horde.message');
}
/* Show the header. */
Prefs_UI::generateHeader($group);
/* Assign variables to hold select lists. */
if (!$prefs->isLocked('language')) {
$GLOBALS['language_options'] = &$GLOBALS['nls']['languages'];
}
if (!empty($group) && Prefs_UI::groupIsEditable($group)) {
foreach ($prefGroups[$group]['members'] as $pref) {
if (!$prefs->isLocked($pref)) {
/* Get the help link. */
if (!empty($_prefs[$pref]['help'])) {
$helplink =
Help::link(!empty($_prefs[$pref]['shared']) ? 'horde' :
$registry->getApp(), $_prefs[$pref]['help']);
} else {
$helplink = null;
}
switch ($_prefs[$pref]['type']) {
case 'implicit':
break;
case 'special':
require $registry->get('templates',
!empty($_prefs[$pref]['shared']) ? 'horde' : $registry->getApp()) .
"/prefs/$pref.inc";
break;
case 'link': // SDS 2/17/2005
case 'select':
case 'text':
case 'textarea':
case 'password':
case 'enum':
case 'multienum':
case 'number':
case 'floatnumber':
case 'checkbox':
require $registry->get('templates', 'horde')
. '/prefs/' . $_prefs[$pref]['type'] . '.inc';
break;
default: // SDS 2/17/2005
/* Individual applications can create their own
class of UI handler
* just invent your own type. Then place a
file inside your application
* at templates/prefs/yourcustotype.inc which
contains your html for the control
* inside of the lib/prefs.php in your
application, add a function
* handle_yourcustotype an you are done.
Remember - if you have a shared
* preference, your UI will not be available
to other applications.
* Differs from 'special' in that you can
re-use same UI widget
* in your application's preferences. */
$custType = $_prefs[$pref]['type'];
require $registry->get('templates',
$registry->getApp()) . "/prefs/$custType.inc";
break;
}
}
}
require $registry->get('templates', 'horde') . '/prefs/end.inc';
} else {
$columns = array();
if (is_array($prefGroups)) {
foreach ($prefGroups as $group => $gvals) {
if (Prefs_UI::groupIsEditable($group)) {
$col = $gvals['column'];
unset($gvals['column']);
$columns[$col][$group] = $gvals;
}
}
$span = round(100 / count($columns));
} else {
$span = 100;
}
require $registry->get('templates', 'horde') .
'/prefs/overview.inc';
}
}
State ⇒ Assigned
Priority ⇒ 1. Low
Type ⇒ Enhancement
Summary ⇒ Add preference data type support for floating point numbers
Queue ⇒ Horde Base
New Attachment: floatnumber.inc
State ⇒ New
able to handle preferences for several floating point numbers. There
is support for integers and strings but not floating point. Since I
need to use this for several fields, it would be non-ideal to create a
hook for each of these fields.
The change required is quite simple. Add the following code to
horde/lib/Prefs/ui.php right after the case 'number' element.
case 'floatnumber': // SDS 2/16/2005
$num = Util::getPost($pref);
if (floatval($num) != $num) {
$notification->push(_("This value
must be a number."), 'horde.error');
} else {
$updated = $updated |
$save->setValue($pref, $num);
}
break;
Additionally, a file must be added to horde/templates/prefs/ for that
particular data type. I have included the appropriate file in
attachment.
thanks,
Scott.