[cvs] [Wiki] changed: FieldTypeProposal
Jason Felice
jfelice at cronosys.com
Wed Aug 11 19:11:09 PDT 2004
eraserhd Wed, 11 Aug 2004 19:11:09 -0700
Modified page: http://wiki.horde.org/display.php?page=FieldTypeProposal
New Revision: 2.2
@@ -1,601 +1,84 @@
-+ Proposal for Implementation of New Package to Replace Horde_Form_Type:: and Horde_Form_Variable::
++ Horde_Field:: Proposal
-++ Notes
+This page is a proposal for the implementation of new package to replace Horde_Form_Type:: and Horde_Form_Variable::.
-+++ IRC Log From 2004-02-13
-<code>
-<oo> trying to figure out the best way to enable editing of images
- using horde_form image type
-<oo> because at the moment form data being edited and containing an
- image field type, drops the image
-<oo> 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.
-<cjh> seems reasonable enough...
-<cjh> the type should be able to create the /tmp file, and then
- return the final data, but not do the final write-back,
- right?
-<oo> 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.
-<oo> right
-<cjh> hmm.
-<cjh> I'm not entirely happy with the VarRenderer setup right now.
-<cjh> we still have the functions for rendering all the types off
- in one file, which seems clunky
-<cjh> and there are too many places you need to edit to add a new
- type - not intuitive at all.
-<oo> i was just talking about bracking up horde_form into multiple
- files with jan earlier today
-<oo> braking even
-<oo> the hunch being that for each form you only need a handful of
- types right?
-<cjh> breaking :)
-<cjh> right.
-<oo> which *could* be better efficiency that including 100k+ files
-<cjh> yeah. and there are probably other places we can reduce
- overhead in the form libs.
-<oo> hm, no wonder "braking" looked weird too :)
-<cjh> like, do we really need Horde_Form_Type *and*
- Horde_Form_Variable? might be more efficient have variables
- just be instances of types.
-<cjh> (instead of having two instantiated objects for each)
-<oo> i think it could all be folded into horde_form_variable
-<cjh> huh, I was thinking the other way around :)
-<oo> dunno... either way i guess. was just following some language
- logic
-<cjh> true, yeah.
-<cjh> eh, I still think Type makes more sense? dunno.
-<cjh> variables have types, but ...
-<oo> :)
-<Eraserhd> back
-<Eraserhd> Can I put in my $.02 re Horde_UI_VarRenderer:: ?
-<oo> no, it will cost you $.04...
-<oo> inflation
-<Eraserhd> 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.
-<oo> hm?
-<Eraserhd> For example, 'enum' and 'radio' are really different presentations
- of the same type.
-<Eraserhd> And 'multienum' and 'checkboxes'.
-<Eraserhd> And the various different date fields.
-<Eraserhd> 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).
-<Eraserhd> We can pass options to field type and construction type and we use
- that for different representations.
-<cjh> 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.
-<Eraserhd> er s/and construction type/at construction time/
-<cjh> (using our packages to not break BC :)
-<cjh> but I agree that it could be organized more usefully.
-<Eraserhd> works for me.
-<Eraserhd> Would my login work for the wiki?
-<Eraserhd> I want to post my Field:: class and a derived class for examples of
- what to do (and what not to do :).
-<Eraserhd> 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?)
-<cjh> 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
-</code>
+I'm refactoring this page into topics. I had a major wiki breakthrough when doing this for work (as I receive e-mail, enter meeting notes, etc., I now factor into topics which could be different pages or different headings on a page). A really good overview emerges fairly quickly and it works extremely well with something like a project's needs analysis. So let's call it a pattern. -- JasonFelice
-+++ Jay's Field:: Class from Cadre Framework
+++ Proposed Requirements
-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.
++++ Useable Without Horde_Form::
-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 >= <date> AND $field_name <= <date2>". 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.
+There are currently still some interdependencies with Horde_UI_VarRenderer:: and Horde_Form:: which really suck. When working on Horde_UI_Table::, I need to render fields in display mode and I have no form. -- JasonFelice
-<code type="php">
-/* 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 */
++++ No Display Assumptions
-/**
- * The field class is an abstract base class which encapsulates a
- * particular type of field.
- */
-class Field {
- //{{{ properties
+I'm going to take the opportunity of a Text_Wiki design discussion on the PEAR lists to propose we reorganize our whole concept of varrenderers and form types, etc.
- var $name;
+First of all, we should clean up the form types so that they are solely conceptual - no display assumptions/logic/differences in them. No difference between radio and select lists, for example.
- /**
- * Array of information about this field, typically fetched from the
- * <database>fw_fields</database> table.
- */
- var $info;
+Then we have the field renderers/etc., one renderer per type of field (where radio and select lists *are* different) per rendering format supported (XHTML, PDF, etc).
- /**
- * 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();
+It'd be a lot of files, which I've tried to avoid in the past, but it seems the only way to really make this flexible, and to avoid having to parse *too* much code just to get a select widget, for example. This should be useable with things like Horde_UI_Table, the Prefs system, the Blocks configuration, etc., too, without intrisically requiring Horde_Form.
- //}}}
- //{{{ constructor
+I'm not sure where the renderers should live in the class tree.
- /**
- * 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"];
+The Text/Wiki class tree idea that got me thinking about this again looks like
+so:
- 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]));
- }
- }
+<code>
+ Text/Wiki/Parse.php
- $this->options = array_merge($this->options, $addtl_options);
- }
+ Text/Wiki/Parse/Bold.php
+ Text/Wiki/Parse/Code.php
+ Text/Wiki/Parse/Wikilink.php
- //}}}
- //{{{ Display_Name()
+ Text/Wiki/Render.php
- /**
- * 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"];
- }
+ Text/Wiki/Render/DocBook.php
+ Text/Wiki/Render/DocBook/Bold.php
+ Text/Wiki/Render/DocBook/Code.php
+ Text/Wiki/Render/DocBook/Wikilink.php
+</code>
- //}}}
- //{{{ Has_Field_Type_Flag()
+-- ChuckHagenbuch (from e-mail on May 26, 2004)
- /**
- * 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;
- }
++++ Multiple Output Formats
- //}}}
- //{{{ Get_Option()
+In our old framework, we can render a field to plain text, HTML, or !TeX (since that is how we produce PDFs).
- /**
- * 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;
- }
+Here's one question, how do we handle edit formats? We could theoretically have a PDF edit format or a Flash edit format, or more likely some sort of embedded device markup. If we have an output matrix of ( medium * type ), we could be talking about a lot of classes.
- //}}}
- //{{{ Get_Db_Value()
+I propose that we just have one class per output medium, and display or edit is determined by which method is called.
- /**
- * 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;
- }
+-- JasonFelice
- //}}}
- //{{{ Get_Filter_Value()
++++ "Filter" Fields
- /**
- * 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);
- }
+I don't know how to handle this just yet, but in our old framework, we have a Get_Filter_HTML() method. For a date, it returns HTML with a start date field and an end date field (it is a possibly open-ended date range, in other words). The field also has a Get_Filter_SQL() which takes a field name and returns a fragment of an SQL WHERE clause to "filter" by that expression.
- //}}}
- //{{{ Get_Filter_SQL()
+In Horde, we can't assume the back end is SQL. In the old framework, a lot of the Get_Filter_HTML() methods have been factored into separate field types (like a date range field, in this case), so perhaps just a method to retrieve an instance of the filter field type for this field type. The driver model makes it difficult to automagically turn this into a query limiter, though, so it might just not be feasable for Horde.
- /**
- * 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;
- }
+-- JasonFelice
- //}}}
- //{{{ Get_Display_Text()
++++ Field Representation Is Configurable
- /**
- * 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;
- }
+In order to take into account different sorts of representations for the same data type, the fields can accept field-dependant config parameters (the typical $params key/value array). The field can use these to determine things like:
- //}}}
- //{{{ Get_Display_Html()
+* How wide a text field appears.
+* Whether to use radio buttons or a drop-down for an enum.
+* etc.
- /**
- * 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 " ";
- else
- return htmlspecialchars ($value);
- }
+-- JasonFelice
- //}}}
- //{{{ Get_Display_TeX()
++++ Scatter/Gather
- /**
- * 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 = '';
+I think the field class that returns the HTML edit representation needs to have a method for "gathering" the form fields back into the internal representation. An example could be Horde's monthdayyear field, which has three form fields for representing a date value. The gather() method would take this data and return the internal value. -- JasonFelice
- $value = $this->Get_Display_Text ($value);
- return texspecialchars($value);
- }
++++ Careful Attention to Internal Value Representations
- //}}}
- //{{{ Get_Browse_Display_TeX()
+We've had this problem with our old framework, and I see it to some degree in Horde. What does a date look like? If we could pick one form, we constrain the number of interfaces we deal with and therefore the complexity. In our old framework, we have internal (PHP) values, database (PostgreSQL textual representation) values and HTML form values. We assumed when first designing when we were basically just using text inputs that all of these were the same, but that got us into big touble.
- Function Get_Browse_Display_TeX ($value)
- {
- return $this->Get_Display_TeX ($value);
- }
+In Horde, we can make sure the storage driver returns the internal form, so we seal off the database representation into the database-specific driver, which is really nice. We currently do have a problem with the other types of values, though. We should probably always use PEAR Date:: representations because we certainly don't want different fields for UNIX timestamp and more useful date formats.
- //}}}
- //{{{ Get_Browse_Display_Html()
+-- JasonFelice
- /**
- * 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>
-<?php
- }
-
- //}}}
-}
-
-//{{{ Get_Field()
-
-/**
- * Finds a field in the fields table, determines it's class, and instantiates
- * the appropriate class for that field. It is assumed that the class name
- * in the database is the named of a class derived from the Field class. The
- * class must already exist or be in a file named
- * <filename>class.<replaceable/classname/.php</filename>.
- *
- * 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];
-}
-
-//}}}
-
-</code>
-
-+++ Email from 5/26/04:
-
-<code>
-Date: Wed, 26 May 2004 14:59:25 -0400 [02:59:25 PM EDT]
-From: Chuck Hagenbuch
-Subject: [dev] Horde_Form_Type, VarRenderer, and what to do about it all.
-
-I'm going to take the opportunity of a Text_Wiki design discussion on the PEAR
-lists to propose we reorganize our whole concept of varrenderers and form
-types, etc.
-
-First of all, we should clean up the form types so that they are solely
-conceptual - no display assumptions/logic/differences in them. No difference
-between radio and select lists, for example.
-
-Then we have the field renderers/etc., one renderer per type of field (where
-radio and select lists *are* different) per rendering format supported (XHTML,
-PDF, etc).
-
-It'd be a lot of files, which I've tried to avoid in the past, but it seems the
-only way to really make this flexible, and to avoid having to parse *too* much
-code just to get a select widget, for example. This should be useable with
-things like Horde_UI_Table, the Prefs system, the Blocks configuration, etc.,
-too, without intrisically requiring Horde_Form.
-
-I'm not sure where the renderers should live in the class tree.
-
-The Text/Wiki class tree idea that got me thinking about this again looks like
-so:
-
- Text/Wiki/Parse.php
-
- Text/Wiki/Parse/Bold.php
- Text/Wiki/Parse/Code.php
- Text/Wiki/Parse/Wikilink.php
-
- Text/Wiki/Render.php
-
- Text/Wiki/Render/DocBook.php
- Text/Wiki/Render/DocBook/Bold.php
- Text/Wiki/Render/DocBook/Code.php
- Text/Wiki/Render/DocBook/Wikilink.php
-</code>
More information about the cvs
mailing list