6.0.0-beta13
4/12/26
  • Proposal for Implementation of New Package to Replace Horde_Form_Type:: and Horde_Form_Variable::

++ Notes

+++ IRC Log From 2004-02-13

trying to figure out the best way to enable editing of images

            using horde_form image type

because at the moment form data being edited and containing an

            image field type, drops the image

the idea i'm following right now would be that the image data

            is copied from the vfs store to the /tmp dir for

            display/modification by the image field. then upon submission

            copied back.

seems reasonable enough...

the type should be able to create the /tmp file, and then

             return the final data, but not do the final write-back,

             right?

only thing is, this would have to be triggered by the

            renderer. because the form type doesn't know about the old

            data being loaded until it is rendered.

right

hmm.

I'm not entirely happy with the VarRenderer setup right now.

we still have the functions for rendering all the types off

             in one file, which seems clunky

and there are too many places you need to edit to add a new

             type - not intuitive at all.

i was just talking about bracking up horde_form into multiple

            files with jan earlier today

braking even

the hunch being that for each form you only need a handful of

            types right?

breaking :)

right.

which could be better efficiency that including 100k+ files

yeah. and there are probably other places we can reduce

             overhead in the form libs.

hm, no wonder "braking" looked weird too :)

like, do we really need Horde_Form_Type and

             Horde_Form_Variable? might be more efficient have variables

             just be instances of types.

(instead of having two instantiated objects for each)

i think it could all be folded into horde_form_variable

huh, I was thinking the other way around :)

dunno... either way i guess. was just following some language

            logic

true, yeah.

eh, I still think Type makes more sense? dunno.

variables have types, but ...

:)

back

Can I put in my $.02 re Horde_UI_VarRenderer:: ?

no, it will cost you $.04...

inflation

hehe. I agree with pretty much everything. I keep thinking that

       we have too many types.  We need to coalesce different

       representations of the same type.

hm?

For example, 'enum' and 'radio' are really different presentations

       of the same type.

And 'multienum' and 'checkboxes'.

And the various different date fields.

Part of the reason I think that, though, is because we have a

       similar thing in our old framework, but it has slightly different

       semantics which make it much more useful.  They represent *data*

       types not *field* types in our system, and the result is that a

       data type knows how to draw itself, accept form input, accept

       "query" input (like a range for the value for ints or a keyword

       expression for strings).

We can pass options to field type and construction type and we use

       that for different representations.

I kind of think that we need to start this as a new package of some

      sort, so that we can develop it and *then* selectively migrate

      existing apps to it.

er s/and construction type/at construction time/

(using our packages to not break BC :)

but I agree that it could be organized more usefully.

works for me.

Would my login work for the wiki?

I want to post my Field:: class and a derived class for examples of

       what to do (and what not to do :).

The other thing was that I was thinking about this export

       definition thing, and I think you should be able to query a

       Field::-derived class for different representations (e.g. Do we

       want to export as UNIX timestamp or mm/dd/yyyy?)

you can do that with the date type in Horde_Form now, I believe.

<oo_> correct

<oo_> plus don't know what i missed but enum/radio are only different in how

      they are rendered.. in the horde_form_type classes one inherits from

      the other

+++ Jay's Field:: Class from Cadre Framework

This is an example from the Cadre framework. I'm certainly not suggesting just taking this and using it, but this class has weathered almost four years of service, so I'm posting it here for discussion in this context.

Here are some notes:

  • The Get_Field() function at the end is equivalent to Horde's ::singleton() method.

  • Data describing the field is stored in a table in the database. Fields are loaded by name. We design our database schemas so that all fields have unique names (we prepend the field name with the table name, except with foreign keys where we use the same name as the referenced field). This way we can just say to our framework "get me a user_id" and it will return a class which can:

  • Render the value as text (::Get_Display_Text()), HTML (::Get_Display_Html()), or TeX (::Get_Display_TeX())

  • Render the value as an HTML field for input. (::Get_Edit_Html())

  • Convert the HTML representation back into the data representation (::Get_Db_Value()).

  • Render a search field for the value (::Get_Filter_Html()). For a date field, this might render a start date and end date fields.

  • Convert the HTML filter representation into an SQL filter expression. (In the previous example, it might return "$field_name >= AND $field_name <= ". If all blank, it will return "TRUE". (::Get_Filter_SQL()).

  • Validate the field input and return an error message if it doesn't pass (::Validate()).

  • "Options" and "Flags" control how the field behaves, and these are stored in the database. You can override them when calling Get_Field() if necessary.

/* CADRE - Cronosys web application framework.

  • Copyright (C) 2002 Cronosys, LLC.

  • $Id: class.Field.php,v 1.23 2004/01/22 13:52:39 jasonf Exp $

  • This program is free software; you can redistribute it and/or modify

  • it under the terms of the GNU General Public License as published by

  • the Free Software Foundation; either version 2, or (at your option)

  • any later version.

  • This program is distributed in the hope that it will be useful,

  • but WITHOUT ANY WARRANTY; without even the implied warranty of

  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

  • GNU General Public License for more details.

  • You should have received a copy of the GNU General Public License

  • along with this software; see the file COPYING. If not, write to

  • the Free Software Foundation, Inc., 59 Temple Place, Suite 330,

  • Boston, MA 02111-1307 USA */

/**

  • The field class is an abstract base class which encapsulates a

  • particular type of field.

*/

class Field {

//{{{ properties



var $name;



/**

 * Array of information about this field, typically fetched from the

 * <database>fw_fields</database> table.

 */

var $info;



/**

 * This is a name => value array of field options.  This is parsed from

 * field_type or optionally passed in the constructor.

 * @var array $options

 */

var $options = array();



//}}}

//{{{ constructor



/**

 * Construct a new field object.

 *

 * @param array $info           This is a key => value array of items

 *                              describing the field.

 * @param array $addtl_options  These are additional options which override

 *                              any options found in $info['field_type'].

 */

Function Field ($info, $addtl_options = array())

{

$this->info = $info;

$this->name = $this->info["field_name"];



    if (isset($info['field_type']))

        {

        $work = $info['field_type'];

        while (strlen($work) > 0)

            {

            if (preg_match("/^([a-zA-Z0-9_]+)(?:(=)(\"(?:[^\"\\\\]|\\\\.)*\"|[^,]*))?,?/", $work, $m))

                {

                if (isset($m[2]) && $m[2] == '=')

                    {

                    if (substr($m[3],0,1) == '"' && substr($m[3],-1) == '"')

                        {

                        $value = preg_replace('/\\\\(.)/', '\\1',

                                              substr($m[3],1,

                                                     strlen($m[3])-2));

                        }

                    else

                        $value = $m[3];

                    }

                else

                    $value = true;

                $this->options[$m[1]] = $value;

                }

            else

                break;

            $work = substr($work, strlen($m[0]));

            }

        }



    $this->options = array_merge($this->options, $addtl_options);

}



//}}}

//{{{ Display_Name()



/**

 * This method returns a name for the field as it would appear to a

 * user on a report or an entry screen.  For example, for the database

 * field <database>user_name</database>, it might return "User Name".

 *

 * @returns the field's display name.

 */

Function Display_Name ()

{

return $this->info["field_display_name"];

}



//}}}

//{{{ Has_Field_Type_Flag()



/**

 * Determines if <varname/$flag/ is one of the comma-separated values in

 * this field's <database/field_type/ field.

 *

 * @param $flag the flag to check for.

 * @returns true if the flag is present, false otherwise.

 */

Function Has_Field_Type_Flag ($flag)

{

    return isset($this->options[$flag]) && $this->options[$flag] !== false;

}



//}}}

//{{{ Get_Option()



/**

 * Retrieves the value of an option as supplied in the `field_type' flag

 * from the database.

 *

 * @param string $name          This is the option's name.

 * @param mixed $default        This is the deafult value of the option.

 * @return mixed the option's value or null if not present.

 */

Function Get_Option ($name, $default = null)

{

    return isset($this->options[$name]) ? $this->options[$name] : $default;

}



//}}}

//{{{ Get_Db_Value()



/**

 * This method converts a representation of the field's value as it

 * would be received from an HTML form to a form acceptable for the

 * database.

 *

 * For example, a boolean field might appear on an edit form as a

 * checkbox and return the value "On" if the box is checked.  For a 

 * boolean field type, this method would convert "On" to "t", which is

 * the way the PostgreSQL database likes to receive its boolean true.

 *

 * This method is not responsible for quoting or escaping the value.

 *

 * @param value the value received from the HTML form.

 * @returns the value you would send to the database.

 */

Function Get_Db_Value ($value)

{

if (isBlank($value) && $this->Has_Field_Type_Flag('nullable'))

    return null;

return $value;

}



//}}}

//{{{ Get_Filter_Value()



/**

 * This method converts the HTTP post data created by the HTML from 

 * <function/Field::Get_Filter_Html()/ to a "database" value suitable

 * for passing around and/or sending back to <function/Get_Filter_Html()/.

 *

 * @param value the value received from the HTML form.

 * @returns the value you would pass around.

 */

Function Get_Filter_Value ($value)

{

    return $this->Get_Db_Value ($value);

}



//}}}

//{{{ Get_Filter_SQL()



/**

 * This method converts a filter value to SQL code to filter a query. The

 * value here is not the post value, but the value as might be returned

 * by <function/Field::Get_Filter_Value()/.

 *

 * By default, we work like a text field with a keyword search.

 *

 * @param $value the filter value to convert.

 * @returns an expression useful for an SQL "WHERE" clause.

 */

Function Get_Filter_SQL ($column, $value, &$error_message)

{

    if (is_null($value))

        return "TRUE";

$ret = dbKeywordClause($column, $value, $error_message);

if (!isBlank ($error_message))

    $error_message .= ' in "'.$this->Display_Name() .'"';

return $ret;

}



//}}}

//{{{ Get_Display_Text()



/**

 * This method translates the value into a textual representation that

 * you might display to a user.  This method does not use HTML or any

 * other sort of markup; for that, see the 

 * <function>Get_Display_Html</function> method.

 *

 * @param value the value retreived from the database.

 * @returns a human-readable, plaintext representation of the value.

 */

Function Get_Display_Text ($value)

{

return $value;

}



//}}}

//{{{ Get_Display_Html()



/**

 * This method translates a database value into an HTML representation

 * that you might display to a user.  This method uses HTML markup for

 * formatting if necessary.

 *

 * Implementors of this method in derived classes should not that any

 * HTML special characters should be properly escaped with 

 * <function>htmlspecialchars</function>().

 *

 * @param value the value retreived from the database.

 * @returns a human-readable, marked-up representation of the value.

 */

Function Get_Display_Html ($value)

{

$value = $this->Get_Display_Text ($value);

if ( IsBlank ($value) )

    return "&nbsp;";

else

    return htmlspecialchars ($value);

}



//}}}

//{{{ Get_Display_TeX()



/**

 * This method translates a database value into an TeX representation

 * that you might display to a user.  This method uses TeX markup for

 * formatting if necessary.

 *

 * Implementors of this method in derived classes should ensure that the

 * returned string is properly escaped for TeX output or call this base

 * implementation.

 *

 * @param value the value retreived from the database.

 * @returns a human-readable, marked-up representation of the value.

 */

Function Get_Display_TeX ($value)

{

$ret = '';



$value = $this->Get_Display_Text ($value);

return texspecialchars($value);

}



//}}}

//{{{ Get_Browse_Display_TeX()



Function Get_Browse_Display_TeX ($value)

{

return $this->Get_Display_TeX ($value);

}



//}}}

//{{{ Get_Browse_Display_Html()



/**

 * This method translates a database value into an HTML representation

 * in much the same way that <function>Get_Display_Html</function>() does;

 * however, this method may truncate the data or otherwise abbreviate it.

 * This is designed to use from <classname>Browser</classname> tables.

 *

 * @param value the value retreived from the database.

 * @returns a human-readable, marked up representation of the value.

 */

Function Get_Browse_Display_Html ($value)

{

return $this->Get_Display_Html ($value);

} // Function Get_Browse_Display_Html ()



//}}}

//{{{ Get_Edit_Value()



/**

 * This method retreives the most appropriate value for the field.  Note

 * that if validation failed and the <varname/$base/ doesn't start with

 * `post' or `view', it won't get the proper value.  That's okay, because

 * everything should be under `post' or `view' anyway.

 */

Function Get_Edit_Value ($value, $base = 'post[data]')

{

global $post, $view, $store;



$postvar = $base."[".$this->info[field_name]."]";

if ( $post[validation_failed] )

    eval("\$value = \$$postvar;");

elseif ( IsSet($store[$postvar]) )

    $value = $store[$postvar];



return ($value);

}



//}}}

//{{{ Get_Edit_Html()



/**

 * This method translates a database value into an editable HTML

 * representation.  It generates the necessary form control or controls

 * and populates them with <parameter>$value</parameter>.

 *

 * @param value the current value of the field.

 * @param base a hack to allow more than one field with the same name

 *	    on one form.

 * @returns the HTML for the edit controls.

 */

Function Get_Edit_Html ($value, $base = 'post[data]')

{

$postvar = $base."[".$this->info[field_name]."]";



$value = $this->Get_Edit_Value($value, $base);



return "<INPUT TYPE=\"TEXT\" NAME=\"$postvar\" VALUE=\"" .

    htmlspecialchars ($value) . "\" SIZE=\"" .

    $this->info["field_width"] . "\"" . ( $this->Has_Field_Type_Flag("small") ? " class=\"smalledit\"" : "" ) . ">";

}



//}}}

//{{{ Get_Filter_Html()



/**

 * This method translates a database value into editable HTML control(s)

 * of the sort you would use to search based on that field as opposed

 * to the sort you would use to edit a value in that field.  This allows

 * us to search on a date range for a date field, for example.

 *

 * @param $value the default filter value.

 * @param $base the variable's base.

 * @returns the HTML for the edit control(s).

 */

Function Get_Filter_Html ($value, $base = 'view[filter]')

{

return $this->Get_Edit_Html ($value, $base);

}



//}}}

//{{{ Validate()



/**

 * This method is called before processing post data to determine if the

 * HTML form value is valid.  It is meant to be overridden by date types,

 * for example, to check that the date format is valid before passing

 * the information to the database.

 *

 * @param value the HTML form value to validate.

 * @returns an empty string if the input is valid; otherwise, a friendly

 *	    error message.

 */

Function Validate ($value)

{

return "";

}



//}}}

//{{{ Database_Interface()



/**

 * This returns the interface that we use for dispatching field types.

 */

Function Database_Interface ()

{

?>

<table name="fw_field">

<field name="field_name" type="text" allowNulls="false"/>

<field name="field_display_name" type="text"/>

<field name="field_edit_type" type="text" allowNulls="false"/>

<field name="field_width" type="integer"/>

<field name="field_options" type="text"/>

<index type="unique">

    <indexField name="field_name"/>

</index>

</table>
class.. * * If the field does not exist in the database, some reasonable guesses are * made - a bare Field class is implemented, the field's caption is set to * $name. * * The return value is cached, so calling this multiple times for the same * field isn't a performance issue. * * @param string $name the name of the field. * @param array $field_options allows a screen to send custom info to a * field object. * @returns an instance of a field type class. */ Function Get_Field ($name, $field_options = array()) { global $db; global $field_cache; if ( !IsBlank ($field_cache[$name]) ) return $field_cache[$name]; $r = $db->Query ("SELECT * FROM fw_field WHERE field_name = '" . addslashes ($name) . "';"); if ( !$r || $r->Row_Count () <= 0 ) { $info = array ( "field_name" => $name, "field_display_name" => $name, "field_displayable" => "t", "field_edit_type" => "Field", "field_width" => 20 ); } else $info = $r->Fetch_Row (0); if ( $r ) $r->Free (); if ( IsBlank ($info["field_edit_type"]) ) $info["field_edit_type"] = 'Field'; include_once "class.$info[field_edit_type].php"; $field_cache[$name] = new $info['field_edit_type'] ($info, $field_options); return $field_cache[$name]; } //}}}