Drupal 7 - Construct clean URL from form post - forms

I am creating a custom module in Drupal 7 that is a job search. I am wanting to make it so you can specify a clean URL to perform the search like so:
www.example.com/job-board/cardiology/california
Where "cardiology" is the "specialty" variable and "california" is the "location" variable.
now I know how to do this as far as mapping the variables in my hook_menu and accessing the variables. The problem is how do I create a search form that creates the clean url when you submit it? In other words if I had this:
<form method="get" action="job-board">
<select name="specialty">
<option value="cardiology">Cardiology</option>
<option value="some_other_value">Some Other Value</option>
</select>
<select name="location">
<option value="california">California</option>
<option value="some_other_state">Some Other State</option>
</select>
<input type="submit">
</form>
and I submit it, it will go to www.example.com/job-board?specialty=cardiology&location=california instead of www.example.com/cardiology/california. How can I make the form construct a clean URL?

You should create that form using Drupal's Form API and assign a custom submit callback to it.
In that submit callback, you can perform validation and if everything is OK, redirect the user to the appropriate URL.
See the source code of Examples modules, specifically one related to Form API in order to learn more about it.
In short, here's what you would do:
/**
* Sample form.
*/
function MYMODULE_sample_form() {
$form['specialty'] = array(
'#title' => t('Specialty'),
'#type' => 'textfield',
'#required' => TRUE,
);
$form['state'] = array(
'#title' => t('State'),
'#type' => 'select',
'#options' => array(
'california' => t('California')
),
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Submit the form'));
return $form;
}
/**
* Sample form submit processing.
*/
function MYMODULE_sample_form_submit($form, &$form_state) {
$specialty = $form_state['values']['specialty'];
$state = $form_state['values']['state'];
drupal_goto($specialty . '/ ' . $state);
}

Related

Form Framework: Render and send "headless" with JSON transfer

I want to use the form framework with vue.js. Instead of rendering with fluid, I prepare and assign the form elements as a json object, which vue loads async from my API controller.
My basic implementation of a single text field works so far, but "formstate->getFormValues()" remains empty. So I guess, the binding between my form and the request fails.
The following example is just the basic principle:
Controller: Prepare form fields as array and return json to the requesting frontend application
class FormFrontendApiController extends FormFrontendController
{
public function renderAction(): ResponseInterface
{
$persistenceIdentifier = 'EXT:test/Resources/Private/FormDefinitions/test.form.yaml';
$formConfiguration = $this->formPersistenceManager->load($persistenceIdentifier);
$factory = GeneralUtility::makeInstance(ArrayFormFactory::class);
$formDefinition = $factory->build($formConfiguration);
$formDefinition->setIdentifier($formDefinition->getIdentifier());
$formRuntime = GeneralUtility::makeInstance(HeadlessFormRuntime::class);
$formRuntime->setFormDefinition($formDefinition);
$formRuntime->setRequest($this->request);
$formRuntime->initialize();
$formState = $formRuntime->getFormState();
$finisherResponse = $formRuntime->run();
$elements = $formRuntime->getFormDefinition()->getElements();
$hashService = GeneralUtility::makeInstance(HashService::class);
$stateHash = $hashService->appendHmac(base64_encode(serialize($formState)));
$currentPageIndex = $formRuntime->getCurrentPage() ? $formRuntime->getCurrentPage()->getIndex() : 0;
$currentPageId = $currentPageIndex + 1;
$elements = $formDefinition->getPageByIndex($currentPageIndex)->getElements();
$requestHash = $this->mvcPropertyMappingConfigurationService->generateTrustedPropertiesToken(
['tx_form_formframework[test][text-1]', 'tx_form_formframework[test][__currentPage]'],
'tx_form_formframework'
);
$formFields = [
[
'type' => 'Hidden',
'name' => sprintf('tx_form_formframework[%s][__state]', $formDefinition->getIdentifier()),
'defaultValue' => $stateHash
],
[
'type' => 'Hidden',
'name' => 'tx_form_formframework[__trustedProperties]',
'defaultValue' => $requestHash
],
[
'type' => 'Hidden',
'name' => sprintf('tx_form_formframework[%s][__currentPage]', $formDefinition->getIdentifier()),
'defaultValue' => $currentPageId
]
];
$formDefinition->setRenderingOption('controllerAction', 'perform');
return $this->jsonResponse(json_encode($formFields));
}
}
The rendered frontend contains the following necessary form fields:
<input type="hidden"
name="tx_form_formframework[test][__state]"
value="TzozOToiVFlQTzNcQ01TXEZvcm1cRG9tYWluXFJ1bnRpbWVcRm9ybVN0YXRlIjoyOntzOjI1OiIAKgBsYXN0RGlzcGxheWVkUGFnZUluZGV4IjtpOjA7czoxMzoiACoAZm9ybVZhbHVlcyI7YTowOnt9fQ==aa9f11798f986845c2e54234794c5197446d74dc" />
<input type="hidden"
name="tx_form_formframework[__trustedProperties]"
value="{"test":{"text-1":1,"__currentPage":1}}6a81fd31dfb7f29c51873b39ff18544e45c4d7ba" />
<input class=" o-form-field__input"
id="aPIFormular-101-text-1"
type="text"
name="tx_form_formframework[test][text-1]" value="" />
<input type="hidden" name="tx_form_formframework[test][__currentPage]" value="1" />
To test, I just submit the form back to the controller, as a basic post request (no JavaScript fetch).
debug($formState->isFormSubmitted()) returns true
debug($formState->getFormValues()) remains empty
Question:
Is it possible to use the form framework in this way or will I run into huge problems / efforts?

Symfony Forms - entity field with query_builder, no choices_as_values?

I am trying to achieve something very simple but not sure if this is supported, possible or unexpected behaviour. I have a Symfony entity field which loads some data based on the selection of another field. The data is loaded ok but I want the option name and value to be the same. At the moment, it is populating the names ok, but I want the values to be the same as the names (the choices_as_values option in a Symfony choice field). Is that possible in an entity field.
Here is an example code:
$builder
->addEventListener(
Form\FormEvents::POST_SET_DATA,
function (Form\FormEvent $event) {
$attributeData = $event->getData();
$event->getForm()->add(
'group',
'entity',
array(
'class' => 'AppBundle:CategoryAttributeData',
'placeholder' => 'Select a data group',
'label' => 'Attribute Group',
'choice_label' => 'content',
'required' => false,
'query_builder' => function (Repository\CategoryAttributeData $repository) use ($attributeData) {
$queryBuilder = $repository->createQueryBuilder('u')
->select('u')
->where('u.type = :type')
->andWhere('u.group IS NULL')
->setParameter('type', $attributeData->getType())
;
return $queryBuilder;
}
)
);
}
)
;
The output is:
<select id="attribute_data_group" name="attribute_data[group]" class="form-control">
<option value="">Select a data group</option>
<option value="1">Cars</option>
<option value="2">Electronics</option>
<option value="3">Furniture</option>
</select>
What I am trying to achieve is:
<select id="attribute_data_group" name="attribute_data[group]" class="form-control">
<option value="">Select a data group</option>
<option value="Cars">Cars</option>
<option value="Electronics">Electronics</option>
<option value="Furniture">Furniture</option>
</select>
Since this field is populated via an event listener (because it depends on another field value) I cannot add a view transformer in here.
Any suggestions?
This is actually more complicated than it would seem on the surface. The Entity form type assumes that the "values" of the choice selector are the unique identifier for an entity, this allows the form type (and its associated transformers) to find and transform the passed values from and to the relevant entities. So the first question is - can you uniquely identify your entity by the string "Cars", or "Electronics"? If you can't, then the next question is how were you planning on converting that string into the entity?
If you can, then things are relatively easier. Effectively you need to provide a different ChoiceList implementation for the choice field type - this is effectively what the Entity type already does. I'm not familiar with the correct way to do this and I believe the method changed between Symfony 2.6 and 2.7, but I would look into classes like Symfony\Bridge\Doctrine\Form\Type\EntityType as well as the base Symfony\Component\Form\Extension\Core\Type\ChoiceType.
Short version: it's not wholly straightforward but definitely possible.
It can be done with the option 'choice_value', for example:
'choice_value' => function (?Team $team) {
return $team ? $team->getBesoccerId() : '';
}
It's 100% compatible with the 'query_builder' option. Continuing the same example:
->add('team', EntityType::class, array(
'class' => Team::class,
'label' => 'Equipo',
'query_builder' => function (EntityRepository $er) use ($lang) {
return $er->createQueryBuilder('t')
->andWhere('t.besoccerId IS NOT NULL')
->andWhere('i18n.cod_lang = :cod_lang')
->setParameter('cod_lang', $lang)
->leftJoin('t.translations', 'i18n')
->orderBy('t.sort', 'ASC');
},
'choice_label' => 'name',
'choice_value' => function (?Team $team) {
return $team ? $team->getBesoccerId() : '';
}
))
You can find more information in the documentation.

How to populate zend form data of multi array input fields

Hi I have the following form in zend
<?php
/**
* Admin/modules/admin/forms/TransportRoute.php
* #uses TransportRoute Admission Form
*/
class Admin_Form_TransportRoute extends Zend_Form
{
public function init()
{
$this->setMethod('post');
$stopageDetailsForm = new Zend_Form_SubForm();
$stopageDetailsForm->setElementsBelongTo('transport_route_stopage');
$sd_stopage = $this->CreateElement('text','stopage')
->setAttribs(array('placeholder'=>'Stopage Name', 'mendatory'=>'true'))
->setRequired(true)
->addFilter(new Zend_Filter_StringTrim())
->setDecorators(array( array('ViewHelper') ))
->setIsArray(true)
->addValidators(array(
array('NotEmpty', true, array('messages' => 'Please enter Stopage Name')),
array('stringLength',true,array(1, 6, 'messages'=> 'Stopage Name must be 2 to 40 characters long.'))
));
$sd_stopage_fee = $this->CreateElement('text','stopage_fee')
->setAttribs(array('placeholder'=>'Route Fee', 'mendatory'=>'true'))
->setRequired(true)
->addFilter(new Zend_Filter_StringTrim())
->setDecorators(array( array('ViewHelper') ))
->setIsArray(true)
->addValidators(array(
array('NotEmpty', true, array('messages' => 'Please enter Stopage Fee')),
));
$stopageDetailsForm->addElements ( array (
$sd_stopage,
$sd_stopage_fee,
) );
$this->addSubForm($stopageDetailsForm, 'transport_route_stopage');
//all sub form end here
$id = $this->CreateElement('hidden','id')
->setDecorators(array( array('ViewHelper') ));
$this->addElement($id);
$this->setDecorators(
array(
'PrepareElements',
array('viewScript'))
);
}
}
This is absolutely working fine when I render this form as below:
<div class="row-fluid stopage_block">
<div class="span5">
<?php echo $stopageDetail->stopage;?>
</div>
<div class="span4">
<?php echo $stopageDetail->stopage_fee;?>
</div>
</div>
But at the time of adding a record, I make clones of the div of class "stopage_block" and save them in the database. Now all my concerns are how to populate all the values by using a foreach loop that were inserted through clones of the div.
I have the following arrays
array('stopage' => 'India','stopage_fee' => 5000);
array('stopage' => 'US','stopage_fee' => 50000);
array('stopage' => 'Nepal','stopage_fee' => 2000);
How to populate back these values in my current form by using any loop or something else.
Thanks.
There is a method getSubForms in Zend_Form, you can use it.
Also, I would recommend you to take a look at the following article http://framework.zend.com/manual/1.11/en/zend.form.advanced.html. I guess it's exactly what you are looking for.

zend framework - group elements of a form within a subforms [duplicate]

I would like to be able to add a hidden form field using array notation to my form. I can do this with HTML like this:
<input type="hidden" name="contacts[]" value="123" />
<input type="hidden" name="contacts[]" value="456" />
When the form gets submitted, the $_POST array will contain the hidden element values grouped as an array:
array(
'contacts' => array(
0 => '123'
1 => '456'
)
)
I can add a hidden element to my form, and specify array notation like this:
$form->addElement('hidden', 'contacts', array('isArray' => true));
Now if I populate that element with an array, I expect that it should store the values as an array, and render the elements as the HTML shown above:
$form->populate($_POST);
However, this does not work. There may be a bug in the version of Zend Framework that I am using. Am I doing this right? What should I do differently? How can I achieve the outcome above? I am willing to create a custom form element if I have to. Just let me know what I need to do.
You have to use subforms to get the result you seek. The documentation was quite a ride but you can find it here
Using what I found there I constructed the following formL
<?php
class Form_Test extends Zend_Form {
public function init() {
$this->setMethod('post');
$this->setIsArray(true);
$this->setSubFormDecorators(array(
'FormElements',
'Fieldset'
));
$subForm = new Zend_Form(array('disableLoadDefaultDecorators' => true));
$subForm->setDecorators(array(
'FormElements',
));
$subForm->addElement('hidden', 'contacts', array(
'isArray' => true,
'value' => '237',
'decorators' => Array(
'ViewHelper',
),
));
$subForm2 = new Zend_Form(array('disableLoadDefaultDecorators' => true));
$subForm2->setDecorators(array(
'FormElements',
));
$subForm2->addElement('hidden', 'contacts', array(
'isArray' => true,
'value' => '456', 'decorators' => Array(
'ViewHelper',
),
));
$this->addSubForm($subForm, 'subform');
$this->addSubForm($subForm2, 'subform2');
$submit = new Zend_Form_Element_Submit('submit');
$submit->setValue('Submit');
$this->addElement('submit', 'submit');
}
}
Wich outputs this html:
<form enctype="application/x-www-form-urlencoded" method="post" action=""><dl class="zend_form">
<input type="hidden" name="contacts[]" value="237" id="contacts">
<input type="hidden" name="contacts[]" value="456" id="contacts">
<dt id="submit-label"> </dt><dd id="submit-element">
<input type="submit" name="submit" id="submit" value="submit"></dd></dl></form>
And when submited the post looks like:
array(2) {
["contacts"] => array(2) {
[0] => string(3) "237"
[1] => string(3) "456"
}
["submit"] => string(6) "submit"
}
So thats how you can create the kind of forms you seek. Hope this helps! if you have a question post a comment!
Its quite hackish if you ask me. You basically create subforms but disable there form decorators so just the element gets output. Since the identical contacts[] elements are in different form object zend does'nt overwrite them and it works. But yeah..
Edit: changed it a bit to remove labels and garbage arount the hidden inputs.
To use array notation, you need to specify that the element "belongs to" a parent array:
$form->addElement('hidden', 'contact123', array('belongsTo' => 'contacts', 'value' => '123'));
$form->addElement('hidden', 'contact456', array('belongsTo' => 'contacts', 'value' => '456'));
This indeed seems to be a bug in Zend Framework - the value attribute for an element is properly set to array, but it's ignored when the element renders - it just uses$this->view->escape($value) to output element's html.
I've solved this by implementing a custom helper for such elements:
class My_View_Helper_HiddenArray extends Zend_View_Helper_FormHidden
{
public function hiddenArray($name, $value = null, array $attribs = null)
{
if (is_array($value)) {
$elementXHTML = '';
// do not give element an id due to the possibility of multiple values
if (isset($attribs) && is_array($attribs) && array_key_exists('id', $attribs)) {
unset($attribs['id']);
}
foreach ($value as $item) {
$elementXHTML .= $this->_hidden($name, $item, $attribs);
}
return $elementXHTML;
} else {
return $this->formHidden($name, $value, $attribs);
}
}
}
Which, when used the next way:
$contacts = $form->createElement('hidden', 'contacts')
->setIsArray(true)
->setDecorators(array(
array('ViewHelper', array('helper' => 'HiddenArray')),
));
$form->addElement($contacts);
generates the needed output.
The reason to extend Zend_View_Helper_FormHidden here is just to be able to call the default behaviour if no array value is set ( return parent::formHidden($name, $value, $attribs) ).
Hope this helps someone :)
For the newer versions of ZF you should use https://framework.zend.com/manual/2.1/en/modules/zend.form.elements.html#multicheckbox

Zend Framework: Working with Form elements in array notation

I would like to be able to add a hidden form field using array notation to my form. I can do this with HTML like this:
<input type="hidden" name="contacts[]" value="123" />
<input type="hidden" name="contacts[]" value="456" />
When the form gets submitted, the $_POST array will contain the hidden element values grouped as an array:
array(
'contacts' => array(
0 => '123'
1 => '456'
)
)
I can add a hidden element to my form, and specify array notation like this:
$form->addElement('hidden', 'contacts', array('isArray' => true));
Now if I populate that element with an array, I expect that it should store the values as an array, and render the elements as the HTML shown above:
$form->populate($_POST);
However, this does not work. There may be a bug in the version of Zend Framework that I am using. Am I doing this right? What should I do differently? How can I achieve the outcome above? I am willing to create a custom form element if I have to. Just let me know what I need to do.
You have to use subforms to get the result you seek. The documentation was quite a ride but you can find it here
Using what I found there I constructed the following formL
<?php
class Form_Test extends Zend_Form {
public function init() {
$this->setMethod('post');
$this->setIsArray(true);
$this->setSubFormDecorators(array(
'FormElements',
'Fieldset'
));
$subForm = new Zend_Form(array('disableLoadDefaultDecorators' => true));
$subForm->setDecorators(array(
'FormElements',
));
$subForm->addElement('hidden', 'contacts', array(
'isArray' => true,
'value' => '237',
'decorators' => Array(
'ViewHelper',
),
));
$subForm2 = new Zend_Form(array('disableLoadDefaultDecorators' => true));
$subForm2->setDecorators(array(
'FormElements',
));
$subForm2->addElement('hidden', 'contacts', array(
'isArray' => true,
'value' => '456', 'decorators' => Array(
'ViewHelper',
),
));
$this->addSubForm($subForm, 'subform');
$this->addSubForm($subForm2, 'subform2');
$submit = new Zend_Form_Element_Submit('submit');
$submit->setValue('Submit');
$this->addElement('submit', 'submit');
}
}
Wich outputs this html:
<form enctype="application/x-www-form-urlencoded" method="post" action=""><dl class="zend_form">
<input type="hidden" name="contacts[]" value="237" id="contacts">
<input type="hidden" name="contacts[]" value="456" id="contacts">
<dt id="submit-label"> </dt><dd id="submit-element">
<input type="submit" name="submit" id="submit" value="submit"></dd></dl></form>
And when submited the post looks like:
array(2) {
["contacts"] => array(2) {
[0] => string(3) "237"
[1] => string(3) "456"
}
["submit"] => string(6) "submit"
}
So thats how you can create the kind of forms you seek. Hope this helps! if you have a question post a comment!
Its quite hackish if you ask me. You basically create subforms but disable there form decorators so just the element gets output. Since the identical contacts[] elements are in different form object zend does'nt overwrite them and it works. But yeah..
Edit: changed it a bit to remove labels and garbage arount the hidden inputs.
To use array notation, you need to specify that the element "belongs to" a parent array:
$form->addElement('hidden', 'contact123', array('belongsTo' => 'contacts', 'value' => '123'));
$form->addElement('hidden', 'contact456', array('belongsTo' => 'contacts', 'value' => '456'));
This indeed seems to be a bug in Zend Framework - the value attribute for an element is properly set to array, but it's ignored when the element renders - it just uses$this->view->escape($value) to output element's html.
I've solved this by implementing a custom helper for such elements:
class My_View_Helper_HiddenArray extends Zend_View_Helper_FormHidden
{
public function hiddenArray($name, $value = null, array $attribs = null)
{
if (is_array($value)) {
$elementXHTML = '';
// do not give element an id due to the possibility of multiple values
if (isset($attribs) && is_array($attribs) && array_key_exists('id', $attribs)) {
unset($attribs['id']);
}
foreach ($value as $item) {
$elementXHTML .= $this->_hidden($name, $item, $attribs);
}
return $elementXHTML;
} else {
return $this->formHidden($name, $value, $attribs);
}
}
}
Which, when used the next way:
$contacts = $form->createElement('hidden', 'contacts')
->setIsArray(true)
->setDecorators(array(
array('ViewHelper', array('helper' => 'HiddenArray')),
));
$form->addElement($contacts);
generates the needed output.
The reason to extend Zend_View_Helper_FormHidden here is just to be able to call the default behaviour if no array value is set ( return parent::formHidden($name, $value, $attribs) ).
Hope this helps someone :)
For the newer versions of ZF you should use https://framework.zend.com/manual/2.1/en/modules/zend.form.elements.html#multicheckbox