Question: Is it possible to access to the Form class from an added element?
Example case:
Note: This example makes no sense as it is, but that's not exactly what I'm trying to do: It's just to keep things simple
Suppose to have a custom view helper that wraps an element into a div. Something like:
public function render(ElementInterface $element = NULL) {
return '<div class="myclass">'.$this->view->formElement($element).'<div>';
}
I would like to retrive the class 'myclass' from the element itself and add it to the div only if the form has been submitted. Something like:
public function render(ElementInterface $element = NULL) {
$class='default';
if(isset($_POST['submit'])){
$class=$element->getOption('wrapper_class');
}
return'<div class="'.$class.'">'.$this->view->formElement($element).'<div>';
}
That works (if 'submit' is the name of the submit button) but, if I have two forms into the same page the second form submission will trigger the above condition and the class will be applied.
A workaround could be:
class MyForm extends Form {
public function __construct($name = null){
parent::__construct($name);
$this->add([
'name' => 'myElement',
'type' => MyCustomElement::class,
'options' => [
'triggered_by' => $this->getName(),
'wrapper_class'=>'myClass',
],
]);
$this->add([
'name' => $this->getName(),
'type' => 'submit',
'attributes' => [
'value' => 'Go',
'id' => 'submitbutton',
'class'=>'btn btn-success',
],
]);
}
}
and then: if(isset($_POST[$element->getOption('triggered_by')])){ ... }
But that works good only if the custom element is added directly to the form. If it's added to a fieldset then $this->getName() will return the name of the fieldset. Obviously the name could be added as a string but I would like to avoid it (typos).
The top solution would be to have access to the main form's options/attributes from all the sub-elements but the elements do not extend Form (myElement->extend Element , Form->extend Fieldset->extend Element).
...then...?
Simple answer: no you can't. Elements can also be a part of a fieldset so they're not directly coupled to a form element.
You could take a different approach with your viewhelper. As in: $myHelper::__invoke(Form $form) or $myHelper::setForm(Form $form), which sets the form. From within that method you can check whether or not the form $form::hasValidated() because that tells us that the form was posted. Then from with the $myHelper::render(ElementInterface $element), you could add some logic to add the wrapperclass as in your example. And within your example merge the classes so that the wrapper doesnt replaces all the form element (css) classes.
Related
i have an ChoiceType::class input field in my form with, now just as an example, two choices:
'choices' => ['type1' => '1', 'type2' => '2']
now when the user select type2 i want to add an exta TextType::class inputfield to the form.
But i dont want to show the input field before and i want it to be required if selected type2 and not if selected type1.
I hope it make sense, i try it to to with javascript and set the attribute to hidden or not, but
then the form is not been send because of the required attribute.
I tried it with form events but did not get it to work in that way.
Thanks
You were on the right way, you have to do it in Javascript. You just need to manage the attr required in Javascript so that the form does not block you with something like this:
Remove the required attribute from a field: document.getElementById("id").required = false;
Make a field required : document.getElementById("id").required = true;
And you can check if the form can be sumitted with : document.getElementById("idForm").reportValidity();.
I using implementation of conditional fields with data-attributes, e.g.:
->add('typeField', EnumType::class, [
'label' => 'Type',
'class' => MyTypeEnum::Class,
])
->add('someField', TextField::class, [
'data-controller' => 'depends-on',
'data-depends-on' => 'my_form_typeField',
'data-depends-value' => MyTypeEnum::OTHER->value,
])
On frontend JS stimulus controller show/hide someField depend on typeField value.
And validation() function in object ('data_class' in formType) make custom validation, e.g.:
/**
* #Assert\Callback
*/
public function validate(ExecutionContextInterface $context)
{
if ($this->typeField !== MyTypeEnum::OTHER) {
$context->buildViolation('message')->atPath('typeField')->addViolation();
}
}
I have a SilverStripe site with some code to display a search form. The for allows you to search for something based on 3 things. Problem is, I'm not sure how to get the results to display correctly on a separate page.
My code:
class InstitutionSearchPage extends Page {
}
class InstitutionSearchPage_Controller extends Page_Controller {
private static $allowed_actions = array(
'Search'
);
public function Form() {
$fields = new FieldList(array(
DropdownField::create('DegreeType', 'Degree', QualificationType::get()->filter('ParentID', 0)->map()),
DropdownField::create('Course', 'Course', Qualification::get()->map()),
DropdownField::create('City', 'City', City::get()->map()),
));
$actions = new FieldList(array(
FormAction::create('Search')->setTitle('Find a College')
));
$validator = ZenValidator::create();
$validator->addRequiredFields(array(
'DegreeType' => 'Please select a Degree',
'Course' => 'Please select a course',
'City' => 'Please select a city',
));
$form = new Form($this, 'Search', $fields, $actions, $validator);
$form->setLegend('Criteria')->addExtraClass('form-horizontal')->setAttribute('data-toggle', 'validator');
// Load the form with previously sent data
$form->loadDataFrom($this->request->postVars());
return $form;
}
public function Search() {
return array('Content' => print_r($this->getRequest()->postVars(), true));
}
}
It seems to be displaying results on the same page but gives me a bunch of weird data. For example, I got this when I tested the form: Array ( [DegreeType] => 53 [Course] => 1 [City] => 1 [SecurityID] => 02718d0283e27eeb539eff19616e0b23eadd6b94 [action_Search] => Find a College )
The result is supposed to be an organized list of colleges (along with other data).
I guess that what you are seeing is expected behavior, as the form will post to the Search function, and that one is just returning a print_r of an array with the post vars which will be picked up by the template.
Otherwise, there are a lot of things not corresponding with the Silverstripe default way to handle forms. Please take a look here and change your form accordingly: https://docs.silverstripe.org/en/3.4/tutorials/forms/
For example, give the form the same name as your function (or in your case, change the function name to the form name). Then implement the action function.
I'm trying to understand and get around this whole form collection thing, but the documentation isn't really expansive and I just can't find out how to do some specific things I need.
I will refer to the example in the official manual to explain what i need:
When the collection is created, you get to use a custom fieldset as target:
$this->add(array(
'type' => 'Zend\Form\Element\Collection',
'name' => 'categories',
'options' => array(
'label' => 'Please choose categories for this product',
'count' => 2,
'should_create_template' => true,
'template_placeholder' => '__placeholder_:',
**'target_element' => array(
'type' => 'Application\Form\CategoryFieldset',
)**,
),
));
However, I need to pass an argument to the specific fieldset's constructor, in my case a translator instance in order to be able to translate within the fieldset.
class CategoryFieldset extends Fieldset
{
public function __construct($translator)
}
Fieldset's label: as you can see in the example, the collection outputs all the copies of the fieldset with the same specified label "Category". I would need, instead, to have that label numbered, to show "Category 1", "Category 2" etc. based on the collection's count. Is this even possible?
Thanks for your help!
I checked source of the Collection. The Collection just clones target_element. My solution is simple and works:
class CategoryFieldset extends Fieldset implements InputFilterProviderInterface
{
static $lp = 1;// <----------- add this line
public function __clone() //<------------ add this method
{
parent::__clone();
$oldLabel = $this->elements['name']->getLabel();
$this->elements['name']->setLabel($oldLabel . ' ' . self::$lp++);
}
For the first, do not pass translator to the Fieldset, but use translator outside of the Fieldset. Get the values first, from the form, translate them, then set them back into the form. The bonus is that you keep your form and your translator logic separate.
For the second, use $form->prepare() and then iterate over the Collection.
$form->prepare(); //clones collection elements
$collection = $form->get('YOUR_COLLECTION_ELEMENT_NAME');
foreach ($collection as $fieldset)
$fieldset->get('INDIVIDUAL_ELEMENT_NAME')->setLabel("WHATEVER YOU WANT");
Example:
/*
* In your model or controller:
*/
$form->prepare();
$collection = $form->get('categories');
foreach ($collection as $fieldset)
{
$label = $fieldset->get('name')->getLabel();
$translatedLabel = $translator->translate($label);
$fieldset->get('name')->setLabel($translatedLabel);
}
How can I change the attributes of a input form?
I create with this a input (productform.php):
$this->add(array(
'name' => 'categoryId',
'attributes' => array(
'id' => 'categoryId',
'type' => 'hidden',
'value' => '',
),
));
In a previous page I link to the form and set the special value in the url (....com/form/3).
In the indexcontroller.php I get the form with $form = new ProductForm(); and want edit the value and set the special value from the url.
My idea was the $form->setAttribute('categoryId', 'value'); but that not working.
Thanks.
indexcontroller.php
...
$form = new ProductForm();
$form->setHydrator(new CategoryHydrator());
$form->bind(new Product());
$form->setAttribute('categoryId', 'value');
....
productform.php
...
class ProductForm extends Form
{
public function __construct()
{
parent::__construct('productForm');
$this->setAttribute('action', 'newproduct');
$this->setAttribute('method', 'post');
$this->add(array(
........
$form->get('categoryId')->setValue("value");
Update
So if you just want to fill input, you mean placeholder attribute in html. You can use setAttribute method.
$form->get('categoryId')->setAttribute('placeholder', 'text to show');
The form view helper will not allow arbitrary HTML attributes to be set on the form. This is because it would result in invalid HTML.
If you take a look at Zend\Form\Helper\AbstractHelper there are two properties $validGlobalAttributes and $validTagAttributes which define the allowed tags.
In the case of the form view helper (Zend\Form\View\Helper\Form) The 'valid tag attributes' will be the method, action etc
As you require something custom (for JS possibly?); I would change it to a data- attribute.
$form->setAttribute('data-categoryId', 'value');
The data- is a valid HTML5 attribute which is useful for adding 'domain data' to HTML elements and is really the 'correct' way to do what you require.
The HTML I need:
<label for="text_field_username">User Name</lable>
<input type="text" id="text_field_username" name="text_field_username" class="form-control" />
I want the for of the label to link to the id of the input. This way the user can click on the label to highlight the input. More usefull for checkbox.
Also, less important, I want to had a class to the input field.
What I have tried and does not works for me:
echo $this->formRow($form->get('usr_name'));
I also tried to use partial layout.
echo $this->formElement($element);
Before posting this question I came across this documentation
framework.zend.com/manual/2.2/en/modules/zend.form.view.helpers.html#formlabel
It does not works. It add the for but it point to nothing. !?
View partials help with the rendering of the form, they don't however deal with the properties of the form elements themselves. This is dealt with by the form class and it's collection of form elements (I.e TextElement)
You can use setAttribute('class', 'class name') on any form element
So within the init() method of your form this should work:
$element = $this->getElement('text_field_username');
$element->setAttribute('class', 'class name');
You can also set this in your inherited form helper class like this:
namespace Application\Form;
use Zend\Form\Form;
class NexForm extends Form
{
public function __construct($name = null)
{
parent::__construct('Nex');
$this->setAttribute('method', 'post');
$this->setAttribute(
'enctype',
'multipart/form- data'
);
$this->add(array(
'name' => 'default_widget',
'attributes' => array(
'type' => 'text',
'id' => 'default_widget',
'class' => 'mtz-monthpicker-widgetcontainer',
'required' => 'required',
),
'options' => array(
'label' => 'Please choose the month of records you want to display:',
),
));
}
}
and in your view just call:
$nex=$this->app; //app is the form object we passed from controller to view
echo $this->formElement($nex->get('default_widget'));