I'm having difficulty configuring Zend_Form. I have a Zend_Form sub-class. The form has some required information and some additional information. I want the additional information to be accessible via an array. The submitted data will look something like this:
$formData['required1']
$formData['required2']
$formData['addiotnalData']['aData1']
$formData['addiotnalData']['aData2']
I've Googled this and tried all the suggestions I've found (using subForms and setting the Zend_Form::setIsArray($flag) and Zend_Form::setElementsBelongTo($array) methods), but have not figured out how to do this.
What am I doing wrong? How do I set the names of form elements so that I can access the data with array notation?
Sorted it! The problem is a custom decorator that was being used.
//In
$subForm = new Form_SubForm(); //this can be a Zend_Form or Zend_Form_SubForm
$subForm->setIsArray(true);
$this->addSubForm($subForm, 'subform');
Elements will be rendered with a id of subform-elementname and a name of subform[elementname].
To expand on the answer because $form->setIsArray(TRUE) was not working with my custom decorator for elements. My custom ViewScript decorator was needed for rendering the Zend_Form_Element.
The problem, it was rendering the element name with $this->element->getName(). I had to use $this->element->getFullyQualifiedName() in the ViewScript decorator script.
Related
When removing an element from a form I would usually use
$this->form->remove('foo');
How would I remove an element from a fieldset? for example my $blocks is a fieldset with multiple elements but this doesn't work to remove unwanted elements
$blocks = $this->form->get('page')->get('blocks');
$blocks->remove('active');
When you remove the Form\Element from within a view it will still be present after the creation of the Form that will be passed to the Controller. I strongly suggest for you to be doing a proper OOP approach to your problem. Mainly there's two solutions for this.
The base is always identical, have a Form\Fieldset that matches your Model / Entity. It can have as many child-fieldsets as you need.
Option 1 - Create different Forms and remove elements
Basically this approach would look something like this:
'EntitySubEditForm' => function ($fem) {
$form = new DefaultForm();
$form->get('fieldset')->remove('foo');
$form->get('fieldset')->remove('bar');
return $form;
}
This will basically function like the approach you went, only at the respective place.
The upside to this approach is that you can render your form using $this->formCollection().
The downside to this approach is that even though you may use caching it simply requires more cache-data (hdd-space). And even though it's cheap by now, no reason to waste it ;)
Option 2 - Just don't validate certain fields
You may choose just to ignore some data passed in your special form.
'EntitySubEditForm' => function($fem) {
$form = new DefaultForm();
$form->setValidationGroup(array(
'id', 'name', 'title',
'etc....'
)); // but NOT 'foo' or 'bar'
return $form;
}
This is the approach I'm going. The reason is that I am caching the created Form-Objects so that Form creation is faster. Setting up a validationGroup then allows me to simply ignore values that are sent having these keys. Remember: unvalidated data is NOT passed from Zend\Form.
The downside to this approach is, that you can't render your form using $this->formCollection(), because the elements are still there and would be rendered. You'd have to manually render respective rows using $this->formRow() or even more manually...
more to read...
You may further be interested in the /docs of DoctrineModule #github because it covers a good use-case and describes well how Zend\Form should be used when Forms for specific actions should have different fields. Iirc it uses Option 1.
I use a certain form in several places. In one of them I need to ignore a form element which I set programmatically after the validation.
Because it's just an exception I don't want to create a new form. So I thought, I just remove this element in the controller like:
$myForm->remove('myElement');
The problem is that the form now won't validate. I don't get any errors but the $myForm->isValid() just returns an empty value.
Any ideas what I might be doing wrong?
Ok, finally I found a solution! You can define a ValidationGroup which allows you to set the attributes you'd like to validate. The others are not validated:
$form->setValidationGroup('name', 'email', 'subject', 'message');
$form->setData($data);
if ($form->isValid()) {
...
The first thing I thought about was to remove the validator from your myElement's ValidatorChain. You could get it within the controller with:
$form->getInputFilter()->get( 'myElement' )->getValidatorChain()
It seems like you can't remove from the ValidatorChain, just add. Check this post. Matthew Weier O'Phinney, from Zend, explains why it can't be done and a possible solution for your scenario.
The way I solve this problem, is checking the 'remove condition' when I create the validator in the FormFilter class. If you use annotations I think it doesn't works for you, so Matthew suggestions is the one you should use.
Or you could try the one in this post from #Stoyan Dimov: define two forms, a kind of BasicForm and ExtendedForm. The first one have all the common form elements, the second one is an extended one of the other with the rest of fields. Depending on your condition you could use one or another.
In class ValidatorChain implements Countable, ValidatorInterface, add a new method:
public function remove($name){
foreach ($this->validators as $key => $element) {
$validator = $element['instance'];
if($validator instanceof $name){
unset($this->validators[$key]);
break;
}
}
}
Use like this:
$form->getInputFilter()->get("xxxxx")->getValidatorChain()->remove('xxxxxx');
There must be a validator defined for this particular element that you are trying to remove.
In your controller where you are adding new elements to form, there must be addValidator calling like:
$element->addValidator('alnum');
That is actually causing validation to be failed. So you have removed the element from form but you still have validation defined on that element to be checked.
If you are not able to find this validation adding function in controller, try to see if it has been defined through config file.
You can read further about form validation in zf here: http://framework.zend.com/manual/1.12/en/zend.form.elements.html
I remove the element with:
$form->get('product')->remove('version');
When I post the form I disable the validator on this element with :
$form->getInputFilter()->get('product')->get('version')->setRequired(FALSE);
I need to create a form where the elements (texbox, select, ..) will be dynamically inserted. Right now I have created a empty Form file with just a hidden element and them in my controller I go inserting elements according to certain conditions.
My form file:
class Form_Questions extends Zend_Form {
public function __construct() {
parent::__construct($options);
$this->setName('Questions');
// Hidden Label for error output
$hiddenlabel = new Zend_Form_Element_Hidden('hiddenlabel');
$hiddenlabel->addDecorator(new Form_Decorator_HiddenLabel());
$this->addElements( array($hiddenlabel) );
}
}
In the controller I have something like:
...
$form = new Form_Questions();
$request = $this->getRequest();
if ($request->isPost())
{
$formData = $request->getPost();
if ($form->isValid($request->getPost()))
{
die(var_dump($form->getValues()));
}
}
else
{
//... add textbox, checkbox, ...
// add final submit button
$btn_submit = new Zend_Form_Element_Submit('submit');
$btn_submit->setAttrib('id', 'submitbutton');
$form->addElement($btn_submit);
$this->view->form = $form;
}
The form displays fine but the validation is giving me big trouble. My var_dump() only shows the hidden element that is staticly defined in the Form file. It does not save the dinamic elements so altought I can get them reading what's coming via POST, I can not do something like
$form->getValue('question1');
It behaves like if Zend uses the Form file to store the values when the submit happend, but since the elements are created dinamically they do not persist (either their values) after the post so I can not process them using the standar getValue() way.
I would appreciate any ideas on how to make them "live" til after the post so I can read them as in a normal form.
The form which you are calling isValid() and getValues() methods on is actually your "empty" form - you have instantiated it only a few lines up and haven't added any elements to it at that point.
Remember that POST only sends an array of fieldName => fieldValue type, it doesn't actually send a Zend_Form object.
It is difficult to suggest a new solution without knowing what you are trying to achieve. It is generally better to add all possible elements to your Zend_Form right away, and then only use the ones you need in the view scripts, i.e. echo $this->form->myField;. This will allow isValid() to process all the elements of the form.
It sounds like the form is dynamic in the sense that the questions come from a db, not in then sense that the user modifies the form itself to add new questions.
Assuming this is the case, then I wouldn't add the question fields in the controller. Rather, I'd pass the questions to the form in the constructor and then add the question fields and the validators in the form's init() method. Then in the controller, just standard isPost() and isValid() processing after that.
Or, if you are saying that the questions to be added to the form are somehow a consequence of the hidden label posted, then perhaps you need two forms and two actions: one for the hidden field form and another for the questions.
Ok, the simplest solution I came up with - to my case and considering the really of the code I am currently playing with was to load all the questions I need from the database using a method from my Model (something like fetchQuestions()), them in my controller I go throught the recordset and create the form elements according to the current question of the recordset.
The elements are stacked in an array that is passed to my Form constructor. In the form constructor I read the array and generate all the dynamic elements. I them just echoed the form to the view.
I have not seem why it would be a bad idea to override the Form constructor as I also could not use any of the set/get methods to pass this to my form.
I am using the symfony embedRelation method to embed forms.The code is like this:
public function configure(){
//......
$this->embedRelation('Foos as foos');
$this->getEmbeddedForm('foos')->mergePostValidator(new MenuValidatorSchema());
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
this does not work.
}
When embeding forms in Symfony, the top-level form keeps track of everything. The widget schema, validator schema, defaults, etc. of the embedded form are no longer directly used. You can see what's happening in sfForm::embedForm.
Note that in this case, since it's a post validator, it's perfectly acceptable to add it to the top-level form, i.e.:
$this->mergePostValidator(new MenuValidatorSchema());
If you want the validator schema on the embedded form and it has no current post validator, you can simply do:
$this->validatorSchema['foos']->setPostValidator(new MenuValidatorSchema());
If it has an existing one, you'll have to turn them into an sfValidatorAnd, doing something like:
$this->validatorSchema['foos']->setPostValidator(new sfValidatorAnd(array(
'validators' => array(
$this->validatorSchema['foos']->getPostValidator(),
new MenuValidatorSchema()
)
)));
The syntax of that last option is just one reason why setting post validators on the top-level form is the preferred option when available.
I have a module that builds a form that includes a fieldset. Instead of using the <legend> element to render the fieldset title, I want to place this content in a <div> element instead. But I want to change the behavior only for the form returned by my module, so I don't want to place any new functionality into my theme's template.php file.
In mymod.module I have defined:
// custom rendering function for fieldset elements
function theme_mymod_fieldset($element) {
return 'test';
}
// implement hook_theme
function mymod_theme() {
return array(
'mymod_fieldset' => array('arguments' => array('element' => NULL)),
'mymod_form' => array('arguments' => array())
);
}
// return a form that is based on the 'Basic Account Info' category of the user profile
function mymod_form() {
// load the user's profile
global $user;
$account = user_load($user->uid);
// load the profile form, and then edit it
$form_state = array();
$form = drupal_retrieve_form('user_profile_form', $form_state, $account, 'Basic Account Info');
// set the custom #theme function for this fieldset
$form['Basic Account Info']['#theme'] = 'mymod_fieldset';
// more form manipulations
// ...
return $form;
}
When my page gets rendered, I expected to see the fieldset representing 'Basic Account Info' to be wholly replaced by my test message 'test'. Instead what happens is that the <fieldset> and <legend> elements are rendered as normal, but with the body of the fieldset replaced by the test message instead, like this:
<fieldset>
<legend>Basic Account Info</legend>
test
</fieldset>
Why doesn't my #theme function have the chance to replace the entire <fieldset> element? If I wrap a textfield in this function instead, I am able to completely replace the <input> element along with its label. Furthermore, if I provide an override in my site's template.php for theme_fieldset, it works as expected and I am able to completely replace the <fieldset>, so I know it is possible.
What's different about providing #theme functions to fieldsets inside a module?
Have you tried overriding theme_fieldset() instead of using the #theme function? I believe you could do something like this in your .module file:
function mymodule_fieldset($element) {
// do something;
return $html;
}
This would apply to all fieldsets. You could do some kind of check on $element for the fieldsets you want to affect and then use the default implementation for all others.
Take a look at: http://api.drupal.org/api/function/theme_fieldset/6
I know this is an old post -- but I've run into the same issue. I came up with an ugly work around. This is definitely a bug in the Form API. Maybe my temporary fix will be helpful to someone.
I found (and appended) a bug report here: http://drupal.org/node/225698
Worth checking that before trying my hacky fix.
I'm not sure what the children are in $form['Basic Account Info'] in this example, but basically what you can do is use drupal_render() on that fieldset's children, and then recreate a fieldset array separate from $form['Basic Account Info'], theme it with theme() and pass it back to the form array as markup..
$fieldsetElement = array(
//$child is the key of whatever child you need in the fieldset
//you may have to alter this for multiple children, stringing
//together multiple drupal_render calls on each children
//(#children needs to be a string.. unless your theme can handle the array)
'#children'=>drupal_render($form['Basic Account Info'][$child]),
'#attributes'=>array(),//set real attributes
'#title'=>$form['Basic Account Info']['#title']
);
$form['Basic Account Info'] = array(
'#type'=>'markup',//not really needed, is default
'#value'=>theme('mymod_fieldset',$fieldsetElement)
);
super-duper hacking, likely causes disconnect with form state and potential validation failure -- but both are fixable by trial and error with the form api. I wouldn't recommend this unless you really want to get your hands dirty with PHP and drupal form API, but that's really the only way, unless you can live without variable fieldset themes in your module... Maybe try prefix and suffix?
This is just off the top of my head but maybe the difference is because a fieldset is not a form element but just a seperator or a grouper, if you will. Maybe the #theme callback is only for form elements?
The concept of your code works, meaning you can do what you want to do.
There are some things that can explain why it doesn't work.
The fieldset is not $form['Basic Account Info'].
Need to clear cache.
$form['Basic Account Info']['#theme'] is lost/overridden later in the code execution.
Try to take a look at $form before you do any of the moderations. When I tried to copy your code I run into a bug:
user.pages.inc file needed to be loaded
I was having the same issue.
You need to use #theme_wrappers instead of #theme
'#type' => 'fieldset',
'#theme_wrappers' => array('mymodule_fieldset'),