6.0.0-git
2024-03-19
Last Modified 2016-12-31 by Guest

Horde_Form rewrite/Horde_Field:: Proposal

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

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

"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:

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

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

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 ‹ no, not Chris¹ book.

Beware in_array($_POST/GET[Œinput¹], $whitelist)

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

So the input string Œsecurityhole¹ will match the int number 0.

horde_form overloading:

$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?)

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 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)

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:

// 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();

$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 />

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'));

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 ?
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');

While I would use:

$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
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?

<?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 :).

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:

<?php

$form = new HTML_Quickform2;

$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

$form = new HTML_Quickform2;

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

?>

Simple and easy.

Le 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

$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).

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');

While I would use:

$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?

<?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.

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 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 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 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'));

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
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?

<?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] http://solarphp.com
[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.

2016 thoughts

We have at least 3 separate trains of thought with Horde_Forms

  • generate presentation from DB+Rdo
  • Update Horde_Forms from H3ish PHP4ish style to PSR-autoloadable PHP5/7 OO and losen coupling of form validation and presentation
  • Something with the Ajax framework, at least as an option or addon

Currently, the recommendation from core developers for some ajax use cases is "render simple forms by hand, in a view"

  • A form as a validation context for input in the backend does not need to be presented as a html form - it might be a table of attributes and values, becoming editable only on click
  • Free text input with autocomplete suggestions is quite common today
  • Fields may be invisible of readonly for some users based on application state or permissions
  • apps may break into popup/redbox windows for some cases of "choose existing or create new related item" or load-on-demand dialogues instead of pre-defined conditional subdialogs
  • support relations (Rdo or domain context) with little repetitive coding
  • support on-the-fly validation while typing

Less coupling.

Js/dom

Anything that has a data-edit attribute is an edit trigger. A parent must have data-field (field name) and data-form (form name)
is it sufficient to have any parent of parent to have data-form attribute?

Anything that has data-save is a save trigger.

Some types might need extra handlers like datepicker

Php

The ajax handler must be explicitly registered in ajax application. It guesses prefix_formname_fieldname classname for an injectable object. The field class has a backend and a list of validators and change propagators which are each only defined by interface to allow flexible backends with existing inheritance trees. Otherwise rdo and friends wont work here. A field class also references a type class and horde view templates for active and readonly rendering. Defaults from type, overridable. The form class might be optional for fields as any combination of fields may be displayed anytime. A form or collection with a common save trigger might still be needed to bootstrap new domain items with more than one required field... and we need some context where to store a record's key for ajax interaction. Form may change or unhide more fields depending on valid state