6.0.0-git
2024-05-04

Diff for Project/HordeForm between 34 and 35

[[toc]]

+ Horde_Form rewrite/Horde_Field:: Proposal


+ Horde_Form rewrite/Horde_Field:: Proposal



ThisThis page is a proposal for the implementation of new package to replace Horde_Form_Type:: and Horde_Form_Variable::.



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



++ Proposed Paths/Requirements



+++ Autogeneration



Extend
Extend Rdo (http://cvs.horde.org/framework/Rdo) and Horde_Form to autogenerate basic CRUD forms (create, read, update, delete) based on the metadata already available in Rdo.



+++ Ajax Functionality



Follow up on the Summer of Code Horde_Form project (currently available in the incubator - http://cvs.horde.org/incubator/Horde_Form/). Update the code to use Prototype and 

Scriptaculous for the scripting basis, sync with the current Horde_Form code, and go from there. The ability to have any form switch between active and inactive rendered mode - optionally on a field-by-field basis - using in-place-edit functionality would be very slick.



++++++ Useable Without Horde_Form::



There

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



+++ No Display Assumptions



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'dIt'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

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:



<code>

        Text/Wiki/Parse.php



        Text/Wiki/Parse/Bold.php

        Text/Wiki/Parse/Code.php
        Text/Wiki/Parse/Wikilink.php

        Text/Wiki/Parse/Wikilink.php



        Text/Wiki/Render.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>



-- ChuckHagenbuch (from e-mail on May 26, 2004)



+++ Multiple Output Formats



In our old framework, we can render a field to plain text, HTML, or !TeX (since that is how we produce PDFs).

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.

I propose that we just have one class per output medium, and display or edit is determined by which method is called.

-- JasonFelice

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.



I propose that we just have one class per output medium, and display or edit is determined by which method is called.



-- JasonFelice+++ "Filter" Fields

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.

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.

+++ "Filter" Fields



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.



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.



---- JasonFelice



+++ Field Representation Is Configurable



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:

+++ Field Representation Is Configurable

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:

* How wide a text field appears.

* Whether to use radio buttons or a drop-down for an enum.

* etc.



-- JasonFelice



+++ Scatter/Gather



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



+++ Careful Attention to Internal Value Representations



We'veWe'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.



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.



-- JasonFelice





++ Unsorted Notes



http://uipatternfactory.com/
http://www.lukew.com/resources/articles/PSactions.asp

http://jeffhowden.com/code/css/forms/

http://www.frevvo.com/frevvo/web/home

http://www.smashingmagazine.com/2006/11/11/css-based-forms-modern-solutions/

http://www.formassembly.com/form-garden.php

http://www.jamiehoover.com/?channel=journal&item=11

http://code.google.com/p/repetitionmodel/

http://www.nyphp.org/phundamentals/spoofed_submission.php

http://svn.rubyonrails.org/rails/trunk/actionpack/lib/action_view/helpers/form_helper.rb

http://ajaxian.com/archives/ajform-and-rejax-reloaded



Horde_Form - change tabs and tab javascript to use HTML structure from YUI:

http://developer.yahoo.com/yui/tabview/



Also use this for Prefs?



Also consider Ext tabs:

http://www.yui-ext.com/deploy/ext-1.0-alpha3/docs/



Additional tabs notes:

http://developer.yahoo.com/ypatterns/pattern.php?pattern=moduletabs



avoidavoid serialized form data, send a checksum for it (checksum in session of course)



Horde_View

Horde_View and Horde_Form and sub-views



don't need to create new view objects!

just call $this->render() with the sub page name!

view variables will be maintained







I just discovered a hole in a white list validation technique I borrowed from a
PHP security book &#139; no, not Chris¹ book.

PHP security book &#139; no, not Chris¹ book.



BewareBeware in_array($_POST/GET[&#140;input¹], $whitelist)



Type matters. All input is string type and PHP will try to force type

matching.



So the input string &#140;securityhole¹ will match the int number 0.







horde_formhorde_form overloading:



$form->Enum($name,

$form->Enum($name, $desc, $note, ... (any other params are type config options - use func_get_args/func_num_args if needed - not needed, have __call $args



$form->optionalEnum - non required

$form->requiredEnum - required 

which is default?



expected variables - track in the form, not the Variables object

need a simplified Variables object for incubator Horde_Form

 - remove foo[bar] syntax in favor of $vars->foo->bar ?

 - remove expected variables, getExists, etc.

 - could just be an ArrayObject?



Look at what should be in Type and what in Variable
simplify validate() methods (rename from isValid?)

simplify validate() methods (rename from isValid?)







http://blog.astrumfutura.com/archives/281-Complex-Web-Pages-with-the-Zend-Framework.html





Horde_Form rewrite notes:


http://blog.astrumfutura.com/archives/281-Complex-Web-Pages-with-the-Zend-Framework.html


Horde_Form rewrite notes:

multienum (and checkboxes if any?) get consolidated to Set



enum, radio buttons -> enum








Horde_Form

1 class/file for autoload

__autoload file (all types?)

Horde_Form_Variable_View ?

rename Variable to Field?

keep type separate but make basic api as clean and small as possible

Horde_Form_Type -> standard constructor for all types instead of factory



no
no singletons!



horde_form_type_* -> replace about() methods with doc introspection

http://us2.php.net/reflection

form descriptions - xml, db, php (the list of addVariable() calls)



form descriptions - xml, db, php (the list of addVariable() calls)





TODO

TODO

Actions for buttons (not currently possible)



I know this sounds really dumb but I cant seem to find a solution for

it. How do I attach an action (Horde_Form_Action) to a submit button

in a form that is generated via the Horde_Form class? My objective is

to call a custom javascript function when the user clicks the submit

button on the form.















i am currently in the need of a form-library to process form-data without the ever going

hassle of boring input sanitization and validation. i read thru the Zend-Form proposal

and it seems that its going to be a *big* form-creation quick-form pendant.



i coded a component to do filtering and validation only and transporting values and

errors over session-namespaces to redirect between processing<->view.



the usage is like this:



<code type="php">

// FORM-PROCESSING

$F = new Form('edit');



// common form-data

$F->register('type')->isTableKey('mediatype', 'mediatype_id');

$F->register('collection')->isBool();

$F->register('parts')->isOptional()->isArray()->isTableKey('media', 'media_id');

$F->register('lang')->isTableKey('locale', 'locale_id');

$F->register('country')->isTableKey('locale', 'locale_id');

$F->register('title')->isString();

$F->register('origtitle')->isString();

$F->register('comment')->isString();

$F->register('rating')->isInt(0, 5);



// optional checklists (kind of <select multiple>)

$F->register('genres')->isOptional()->isArray()->isTableKey('genre', 'genre_id');

                $F->register('genres_new')->isOptional()->isArray()->isString();

$F->register('persons')->isOptional()

                        ->isArray()

                        ->isTableKey('person', 'person_id')

                        ->requires('person_roles');

$F->register('person_roles')->isOptional()

                                ->isArray()

                                ->isString();

                                ->isArray()
                                ->isString();

$F->register('persons_new')->isOptional()

                                ->isArray()

                                ->isString()

                                ->requires('person_roles_new');



$F->register('person_roles_new')->isOptional()

                                ->isArray()

                                ->isString();
[...]

[...]



//// working with Zend-Validators, -Filters and Function-Delegates



function fooDelegate($elementName, $elementValue, $formData) {

  return true;

}



$F->register('foo')->validate(new Zend_Validate_xxx())

                   ->filter(new Zend_Filter_yyy())

                   ->delegate('fooDelegate');





try {

  $this->db->beginTransaction();

  $F->validate($_POST);



  $title = $F->value('title');

  ...



  if (($persons= $F->value('persons')) {

    foreach ($persons as $personID) {

      ...

    }

  }



  //or

  $persons = $F->value('persons', array());

  foreach ($persons as $personID) {

    ...

  }



} catch (FormException $e) {

    $this->db->rollBack();

    print_r($F->getErrors());

    $this->redirect('back to edit-form-view');

}






// VIEW

$F = new Form('edit');

if ($F->hasErrors())

  print_r($F->getErrors());



if (!($title = $F->value('title')))

  $title = '<populate from db>';



<input type=text value=$title />

</code>









API



API discussion:








We are in the middle of a discussion Alexey and I about QuickForm2 API for elements

creation and I would like your opinion as well.

At this point, nothing is immutable since we aren't even talking about alpha stage, so

your preferences as users and developers is interesting.



To summarize, Alexey would be in favor of this:



$form->addElement('button', 'aButton', 'Click me please');

$form->addElement('select', 'aSelect', array('1' => 'option 1', '2' => 'option 2'));



While I would be in favor of this:



$form->addElement('button', 'aButton')->setContent('Click me please');

$form->addElement('select', 'aSelect')->setOptions(array('1' => 'option 1', '2' =>

'option 2'));



This is an example for adding a button to a form. The API can get more complex for Date

elements and other javascript aided elements.



My opinion is that we shouldn't mix configuration parameters for elements with other kind

of data they might need.



The other point we are discussing is about the extra parameter in element creation. I

suggest we always use an array, even when there is only one extra parameter. Alexey

suggests that we use a scalar if there is only one extra  parameter. For example, for a

given "Year" element which would only accept one configuration parameter 'startYear',

Alexey would use:



$form->addElement('year', 'aYear', '2007');



While I would use:

$form->addElement('year', 'aYear', array('startYear' => '2007'));


$form->addElement('year', 'aYear', array('startYear' => '2007'));



SoSo my way is more verbose and less writable, but is also more readable and extensible.



Given these examples, are there any opinions or preferences in favor of one or the other

proposed API ?

Thanks in advance,











We are in the middle of a discussion Alexey and I about QuickForm2  

API for elements creation and I would like your opinion as well.

At this point, nothing is immutable since we aren't even talking  

about alpha stage, so your preferences as users and developers is  

interesting.



To summarize, Alexey would be in favor of this:



$form->addElement('button', 'aButton', 'Click me please');

$form->addElement('select', 'aSelect', array('1' => 'option 1', '2'  

=> 'option 2'));



This is the old (and IMO sometimes confusing) style which is (at least

in the current QF) not consistent through the various elements.

While I would be in favor of this:



$form->addElement('button', 'aButton')->setContent('Click me please');

$form->addElement('select', 'aSelect')->setOptions(array('1' =>  

'option 1', '2' => 'option 2'));



This style is more "OO-ish" (*g*) and should also be more consistent in

usage.



The other point we are discussing is about the extra parameter in  

element creation. I suggest we always use an array, even when there  

is only one extra parameter. Alexey suggests that we use a scalar if  

there is only one extra  parameter. For example, for a given "Year"  

element which would only accept one configuration parameter  

'startYear', Alexey would use:



$form->addElement('year', 'aYear', '2007');



WhileWhile I would use:



$form->addElement('year',

$form->addElement('year', 'aYear', array('startYear' => '2007'));



 +1 for this last style because it avoids confusion, too, and especially

also because sometimes later such elements might get a second, third,

... option.














What's really being discussed is the following, will we make the "additional data"

parameter that has different semantics from element to element always an array, so the

first pair of calls above turns into



$form->addElement('button', 'aButton', array('content' => 'Click me please'));

$form->addElement('select', 'aSelect', array('options' => array('1' => 'option 1', '2' =>

'option 2')));



even though the elements in question will never need any additional data other than

'content' and 'options'.













My vote would be for always an array --- even in cases where an

element will only need one piece of scalar data.



As many have already pointed out, changing from scalars to array based

on the individual element leads to confusion about which elements

require which parameter format --- I would even be against the

flexibility of allowing both ways because I think it leads to more

complicated documentation and more room for coding errors.
















I've honestly found the addElement() API to be difficult to work with in

thethe past, so simplifying it would be a welcome change.



However, I don't think "addElement" needs to exist in Quickform2.  Have

you considered an API similar to the following?



<?php



$form = new HTML_Quickform2;

$form['action'] = '/path/to/action.php';

$form['method'] = 'post';

// or

$form['attrs'] = array('action' => '/path/to/action.php', 'method' =>

'post');



$g = $form->group['mygroup'];

$g->text = array('name' => 'aText', 'size' => 50, 'default' => 'whatever');

$g->select = array('name' => 'aSelect', 'options' => array(...));



// or

$g->text(array('name' => 'aText', 'size' => 50))

          ->validate['regex'] =  '/\w+/';

$g->select(array('name' => 'aSelect', 'options' => array())

$select = $g->select['aSelect']; // or $form->select['aSelect'];

$select->options['another'] = 1;

$select->validate['custom'] = 'is_numeric';

?>



Compare this to:



<?php



$form = new HTML_Quickform2('/path/to/action.php', 'method' => 'post');

$form->addGroup('mygroup');

$form->addElement('text', 'aText', array('size' => 50, 'default' =>

'whatever', 'group' => 'mygroup')

    ->validate('regex', '/\w+/');

$sel = $form->addElement('select', 'aSelect', array(...));

$sel->addOption('another', 1);

$sel->validate('custom', 'is_numeric');

?>



In the second example, my eyes are drawn to a bunch of "add*" and it

appears that the important information ('text', 'select') is secondary.

 It takes me a while to figure out what is actually being done.  The

firstfirst example has no superfluous information, but simply sets up the

form
form in a straightforward, no-nonsense manner.



In other words, I've always dreamed of having a simplexml-like API for

accessing/creating forms.  I hate translating what I want to do into

method APIs.  Think of this as bringing the REST model to forms

creation, whereas everything has been XML-RPC based up to this point :).



Another feature request I've had that may be possible is to integrate

some kind of forms creation cache, so that the form need only be created

programmatically once, and then it can on subsequent requests be quickly

recreated from a disk or database cache, skipping all the expensive

method calls, but this is a separate question than the one you asked,

let me know if you'd like a feature request opened on this.



If you do decide to go with the other API format, I am in favor of more

explicit options (associative array/fluent).  I hate relying on Zend IDE

to do auto-completion, it makes debugging far more difficult to do with

just a simple text editor and eyeballs.
















As an addendum to my last message, I just thought of an even clearer
interface:

interface:



<?php



$form<?php

$form = new HTML_Quickform2;



$g = $form->group['mygroup'];


$g = $form->group['mygroup'];

$g->text['aText'] = array('size' => 50);

$g->select['aSelect'] = array('size' => 5', 'options' => array(...));



?>



By using ArrayAccess offsetSet() you can put the element name in

brackets.  This way, the same procedure (array access) is used to both

set and to retrieve elements.  This could also be used to modify values

later if necessary, for the same element, something that I've found
exceedingly difficult to do in the current QF.  Consider the following:

<?php

exceedingly difficult to do in the current QF.  Consider the following:



<?php



$form = new HTML_Quickform2;

$form->text['mytext'] = array('default' => 'whatever');
unset($form->text['mytext']);

?>

Simple and easy.





$form->text['mytext'] = array('default' => 'whatever');

unset($form->text['mytext']);



?>



Simple and easy.











LeLe 16 avr. 07 ? 18:31, Gregory Beaver a Ècrit :



> $g->text(array('name' => 'aText', 'size' => 50))

>           ->validate['regex'] =  '/\w+/';

> $g->select(array('name' => 'aSelect', 'options' => array())

> $select = $g->select['aSelect']; // or $form->select['aSelect'];

> $select->options['another'] = 1;

> $select->validate['custom'] = 'is_numeric';



I like this as well. We have designed what I would call a low-level

API for QuickForm. Built on top of that, it will be easier to add

syntactic sugar like this at a later time.

It's a bit like what jQuery and Prototype did to javascript DOM. PHP5

allows this, we should indeed use it to our advantage IMO (I need to

convince Alexey though :) ).



> Another feature request I've had that may be possible is to integrate

> some kind of forms creation cache, so that the form need only be

> created

> programmatically once, and then it can on subsequent requests be

> quickly

> recreated from a disk or database cache, skipping all the expensive

> method calls, but this is a separate question than the one you asked,

> let me know if you'd like a feature request opened on this.



At the moment, I have no idea how to do that, any ideas would be

welcome.

The good news is that QuickForm2 should be faster than current

QuickForm :)









Another feature request I've had that may be possible is to integrate

some kind of forms creation cache, so that the form need only be created

programmatically once, and then it can on subsequent requests be quickly

recreated from a disk or database cache, skipping all the expensive

method calls, but this is a separate question than the one you asked,

let me know if you'd like a feature request opened on this.



At the moment, I have no idea how to do that, any ideas would be welcome.

The good news is that QuickForm2 should be faster than current QuickForm :)



Hi Bertrand,



I would store element validation information in a simple array, and then

use the renderer to render a PHP-based template (a la Savant) that will

allow setting default values, so that the display process is basically:



<?php

$form->prepareCache();

include $form->getCacheFile();

?>



Validation would work as it currently does.  You'd end up with something

like:



<?php

like:

<?php

$form = new HTML_Quickform2;

$form['name'] = 'whatever';

if ($form->submitted && $form->valid) {

}
}
if ($form->cached) {

    $form->displayCache();

} else {

   // set up the form

}

?>



Things like validation errors can be handled with placeholders as well
(an if() in the template that skips the error HTML).


(an if() in the template that skips the error HTML).







To summarize, Alexey would be in favor of this:


To summarize, Alexey would be in favor of this:

$form->addElement('button', 'aButton', 'Click me please');

$form->addElement('select', 'aSelect', array('1' => 'option 1', '2'

=> 'option 2'));

Well, this has worked in the past and isn't too hard to grasp if the

documentation makes it clear how this works (the current docs are a

bit confusing in this regard). Having the additional parameters be the

constructor args for the element is a fine option. However, it's

always confusing to have lots of parameters to a method and I know I

never remember which is which.
$form->addElement('year', 'aYear', '2007');

$form->addElement('year', 'aYear', '2007');



WhileWhile I would use:



$form->addElement('year', 'aYear', array('startYear' => '2007'));


$form->addElement('year', 'aYear', array('startYear' => '2007'));

So my way is more verbose and less writable, but is also more

readable and extensible.

Extensability is a good thing. Readability is a good thing. I'd vote

for an assoc array over variable numbers of arguments.

Well, people aren't yet familiar with the new QF2 API and Bertrand didn't bother to give

the full picture, so here goes:



We no longer have variable number of parameters to elements' constructors, since neither

Bertrand nor myself were able to remember all the parameter lists, and we actually

*wrote* the abomination. Every constructor is now defined as:

public function __construct($name, $data, $label, $attributes) { ... }



Now, this what this $data thing means changes from element to element because the

elements have different needs. What's *really* being discussed here is the fact that

Bertrand wants to make $data *always* an associative array, thus



$form->addElement('button', 'aButton', array('content' => 'Click me please'));

$form->addElement('select', 'aSelect', array('options' => array('1' => 'option 1', '2' =>

'option 2')));



while I suggest deciding on it on a per-element basis, thus

$form->addElement('button', 'aButton', 'Click me please');

$form->addElement('select', 'aSelect', array('1' => 'option 1', '2' => 'option 2'));



but most certainly

$form->addElement('year', 'aYear', array('startYear' => '2007'));



since that hypothetical 'year' element *may* earn additional parameters in the future

which is highly unlikely in case of Buttons and Selects.











I've honestly found the addElement() API to be difficult to work with in

the past, so simplifying it would be a welcome change.



However, I don't think "addElement" needs to exist in Quickform2.  Have
you considered an API similar to the following?

you considered an API similar to the following?



<?php

<?php

$form = new HTML_Quickform2;

$form['action'] = '/path/to/action.php';

$form['method'] = 'post';

//// or

$form['attrs'] = array('action' => '/path/to/action.php', 'method' =>

'post');



$g = $form->group['mygroup'];

$g->text = array('name' => 'aText', 'size' => 50, 'default' => 'whatever');

$g->select = array('name' => 'aSelect', 'options' => array(...));



// or

$g->text(array('name' => 'aText', 'size' => 50))

         ->validate['regex'] =  '/\w+/';

$g->select(array('name' => 'aSelect', 'options' => array())

$select = $g->select['aSelect']; // or $form->select['aSelect'];

$select->options['another'] = 1;

$select->validate['custom'] = 'is_numeric';

?>

With all due respect, SimpleXML API which you seem to use as inspiration here uses

addAttribute() and addChild() methods for adding attributes and child nodes, it doesn't

work as shown above. ;)

We considered something like that, but decided to go with DOM-like API for now.




We considered something like that, but decided to go with DOM-like API for now.







I assume it is more consistent to always use an array and provide additional setters for

all possible keys, so every element can be added in both ways:



$form->addElement('button', 'aButton', array('content' => 'Click me', 'attributes' =>

array('onClick' => 'alert("Clicked!")')));



or:



$clickbutton = $form->addElement('button', 'aButton');

$clickbutton->setContent('Click me');

$clickbutton->setAttributes('onClick' => 'alert("Clicked!")');



Like this, every public property can actually be set on element construction.





Anyway I see an other point I want to strongly advise to rethink: It looks like the QF2

API will still be quite near to the HTML syntax of every single element, which is

actually not consistent across the elements and their natures. I gather this assumption

from the "options" property of the select element (besides: setOptions() also confuses as

this method has a totally different meaning in many other PEAR classes).



If you put your focus at interchangability of elements, you find 2 different kinds of

lists, which can both have 2 different forms:



1. List with only one element selectable:

- Select element with size=1 (or no size attribute)

- Radiobutton group



2. List with multiple selectable elements:

- Select element with size > 1

- Checkbox group



IMO
IMO those should be treated identically, for example:



$ctry = $form->addElement('select', 'country');

$ctry->setList(array('CH' => 'Switzerland', 'FR' => 'France'));

$ctry->setContent('CH');



If you later decide to change it to a radiogroup, you just have to change the line:

$ctry = $form->addElement('radiogroup', 'country');



Also note that I suggest to use setContent() to define the preselected option rather than

something like $option->setSelected() (which could be left as an alternative). It looks

consistent to me to set the preselected value with the same method for all kinds of

elements/groups, regardless of the way it looks in the generated HTML. So setContent() in

a select box will result in setting the selected attribute, while it will set a value

attribute if applied on a text field.









I



I would even make it:



$clickbutton =

   $form->addElement('button', 'aButton')->setContent('Click

me')->setAttributes('onClick' => 'alert("Clicked!")');



On 4/17/07, Christoph Schiessl <c.schiessl@gmx.net> wrote:



[Show Quoted Text - 58 lines]

On Apr 17, 2007, at 9:58 AM, Markus Ernst wrote:

Alexey Borzov schrieb:

[...]

What's really being discussed is the following, will we make the

"additional data" parameter that has different semantics from

element to element always an array, so the first pair of calls

above turns into

$form->addElement('button', 'aButton', array('content' => 'Click

me please'));

$form->addElement('select', 'aSelect', array('options' => array

('1' => 'option 1', '2' => 'option 2')));

even though the elements in question will never need any

additional data other than 'content' and 'options'.

I assume it is more consistent to always use an array and provide

additional setters for all possible keys, so every element can be

added in both ways:



$form->addElement('button', 'aButton', array('content' => 'Click

me', 'attributes' => array('onClick' => 'alert("Clicked!")')));

I don't like the idea of using arrays for the additional parameters.

Element creation has always been a problem for me in QF1, because

there were to many possible combinations of parameters to remember

all of them.

or:



$clickbutton = $form->addElement('button', 'aButton');

$clickbutton->setContent('Click me');

$clickbutton->setAttributes('onClick' => 'alert("Clicked!")');



Like this, every public property can actually be set on element

construction.

Looks nice. I think that would be easier to use (and to remember).

Also note that I suggest to use setContent() to define the

preselected option rather than something like $option->setSelected

() (which could be left as an alternative). It looks consistent to

me to set the preselected value with the same method for all kinds

of elements/groups, regardless of the way it looks in the generated

HTML. So setContent() in a select box will result in setting the

selected attribute, while it will set a value attribute if applied

on a text field.

Fully agree on that one.

--

Markus





I cannot say I support setting variables and properties with arrays,

it also have been the biggest thing that kept me away from QF for a

long time.





Bertrand Mansion wrote:

Hi all,



We are in the middle of a discussion Alexey and I about QuickForm2 API

for elements creation and I would like your opinion as well.

At this point, nothing is immutable since we aren't even talking about

alpha stage, so your preferences as users and developers is interesting.



To
To summarize, Alexey would be in favor of this:



$form->addElement('button', 'aButton', 'Click me please');

$form->addElement('select', 'aSelect', array('1' => 'option 1', '2' =>

'option 2'));



While I would be in favor of this:



$form->addElement('button', 'aButton')->setContent('Click me please');

$form->addElement('select', 'aSelect')->setOptions(array('1' => 'option

1',1', '2' => 'option 2'));



This is an example for adding a button to a form. The API can get more

complex for Date elements and other javascript aided elements.



My opinion is that we shouldn't mix configuration parameters for

elements with other kind of data they might need.



The other point we are discussing is about the extra parameter in

element creation. I suggest we always use an array, even when there is

only one extra parameter. Alexey suggests that we use a scalar if there

is only one extra  parameter. For example, for a given "Year" element

which would only accept one configuration parameter 'startYear', Alexey

would use:



$form->addElement('year', 'aYear', '2007');



WhileWhile I would use:



$form->addElement('year',

$form->addElement('year', 'aYear', array('startYear' => '2007'));



So my way is more verbose and less writable, but is also more readable

and extensible.



Given these examples, are there any opinions or preferences in favor of

one or the other proposed API ?

Hi Bertrand,



I've honestly found the addElement() API to be difficult to work with in

thethe past, so simplifying it would be a welcome change.



However, I don't think "addElement" needs to exist in Quickform2.  Have

you considered an API similar to the following?



<?php



$form = new HTML_Quickform2;

$form['action'] = '/path/to/action.php';

$form['method'] = 'post';

// or

$form['attrs'] = array('action' => '/path/to/action.php', 'method' =>

'post');



$g = $form->group['mygroup'];

$g->text = array('name' => 'aText', 'size' => 50, 'default' => 'whatever');

$g->select = array('name' => 'aSelect', 'options' => array(...));



// or

$g->text(array('name' => 'aText', 'size' => 50))

         ->validate['regex'] =  '/\w+/';

$g->select(array('name' => 'aSelect', 'options' => array())

$select = $g->select['aSelect']; // or $form->select['aSelect'];

$select->options['another'] = 1;

$select->validate['custom'] = 'is_numeric';

?>



Compare this to:



<?php



$form = new HTML_Quickform2('/path/to/action.php', 'method' => 'post');

$form->addGroup('mygroup');

$form->addElement('text', 'aText', array('size' => 50, 'default' =>

'whatever', 'group' => 'mygroup')

   ->validate('regex', '/\w+/');

$sel = $form->addElement('select', 'aSelect', array(...));

$sel->addOption('another', 1);

$sel->validate('custom', 'is_numeric');

?>



In the second example, my eyes are drawn to a bunch of "add*" and it

appears that the important information ('text', 'select') is secondary.

It takes me a while to figure out what is actually being done.  The

first example has no superfluous information, but simply sets up the

form in a straightforward, no-nonsense manner.



In other words, I've always dreamed of having a simplexml-like API for

accessing/creating forms.  I hate translating what I want to do into

method APIs.  Think of this as bringing the REST model to forms

creation, whereas everything has been XML-RPC based up to this point :).

Using AAs for creating forms is absolutely the way to go. A huge +1

for Greg's suggestions. It is exactly because of the reasons Greg

mentions. The form creation and the actual display of the form should

be separated completely.

Another feature request I've had that may be possible is to integrate

some kind of forms creation cache, so that the form need only be created

programmatically once, and then it can on subsequent requests be quickly

recreated from a disk or database cache, skipping all the expensive

method calls, but this is a separate question than the one you asked,

let me know if you'd like a feature request opened on this.

This is *exactly* what Solar[1] does. You can load[2] forms (or

rather, form "hints", which are just AAs) from SQL table objects and

from XML files and hand them to your "view". This is *made* for MVC.



This complicated API that you suggest seems a really bad idea to me.



Anyways, just my $0.02



[1]
[1] http://solarphp.com

[2]
[2] http://solarphp.com/class/Solar_Form/load()












I don't find any current implementations of forms to have a true cache.

 The problem here is that a form is always changing.  It must respond to

user input, display errors, and so on.  However, the logic of how the

form is constructed is completely static.  Since the only thing we

really need to be able to do is proper validation of form fields, and

then to fill in a few placeholders (default value, error messages if

any), this data can be cached as html, saving the need to construct a

huge tree of objects.



For high-volume sites, this could provide a large gain in

requests/second, whereas building the form from an array is no different

from building it with method calls - except you have the added step of

parsing the array.



Separating logic from view is irrelevant to my suggestion.  Quickform 1.x used renderers to do this, I suppose Quickform2 will have some facility as well.