Nesting Fieldsets under Radio or Checkbox items: Zend Framework 2 - forms

I would like to create a fieldset under each radio/checkbox item.
e.g
Which animals do you like:
[x] Cats
[Fieldset of cat related questions]
[ ] Dogs
[Fieldset of dog related questions]
...
I can create Fieldsets and Forms with ease, but nesting them under each item is causing me some headaches.
Ultimately I had in mind something like this:
$this->add(array(
'type' => 'Zend\Form\Element\Radio',
'name' => 'profile_type',
'options' => array(
'label' => 'Which animals do you like:',
'label_attributes' => array('class'=>'radio inline'),
'value_options' => array(
'1' =>'cats',
'fieldsets' => array(
array(
'name' => 'sender',
'elements' => array(
array(
'name' => 'name',
'options' => array(
'label' => 'Your name',
),
'type' => 'Text'
),
array(
'type' => 'Zend\Form\Element\Email',
'name' => 'email',
'options' => array(
'label' => 'Your email address',
),
),
),
)),
'2'=>'dogs',
'fieldsets' => array(
array(
'name' => 'sender',
'elements' => array(
array(
'name' => 'name',
'options' => array(
'label' => 'Your name',
),
'type' => 'Text'
),
array(
'type' => 'Zend\Form\Element\Email',
'name' => 'email',
'options' => array(
'label' => 'Your email address',
),
),
),
))
),
),
'attributes' => array(
'value' => '1' //set checked to '1'
)
));

Ok I managed to get round this issue using a bit of a hack, but it works.
TestForm.php (note the additional attributes in the elements ('parent' and 'childOf')
class TestForm extends Form
{
public $user_agent;
public $user_ip;
public function __construct($name = null)
{
// we want to ignore the name passed
parent::__construct('profile');
$this->setAttributes(array(
'method'=>'post',
'class'=>'form-horizontal',
));
$this->add(array(
'type' => 'Zend\Form\Element\Radio',
'name' => 'profile_type',
'options' => array(
'label' => 'Which animals do you like:',
'label_attributes' => array('class'=>'radio inline'),
'value_options' => array(
array('label' =>'cats', 'value'=>1, 'parent'=>'cat'),
array('label'=>'dogs', 'value'=>2, 'parent'=>'dog'),
),
),
'attributes' => array(
'value' => '1' //set checked to '1'
)
));
$this->add(array(
'type' => 'Text',
'name' => 'name',
'attributes' => array(
'childOf' => 'cat',
),
'options' => array(
'label' => 'Your name',
),
));
$this->add(array(
'type' => 'Zend\Form\Element\Email',
'name' => 'email',
'attributes' => array(
'childOf' => 'dog',
),
'options' => array(
'label' => 'Your email address',
),
));
}
}
Then extended Zend\Form\View\Helper\FormMultiCheckbox and overwrote the RenderOptions Method (look out for the new optionSpec 'parent') this basically creates a tag e.g.{#cat#} after the parent element:
protected function renderOptions(MultiCheckboxElement $element, array $options, array $selectedOptions,
array $attributes)
{
$escapeHtmlHelper = $this->getEscapeHtmlHelper();
$labelHelper = $this->getLabelHelper();
$labelClose = $labelHelper->closeTag();
$labelPosition = $this->getLabelPosition();
$globalLabelAttributes = $element->getLabelAttributes();
$closingBracket = $this->getInlineClosingBracket();
if (empty($globalLabelAttributes)) {
$globalLabelAttributes = $this->labelAttributes;
}
$combinedMarkup = array();
$count = 0;
foreach ($options as $key => $optionSpec) {
$count++;
if ($count > 1 && array_key_exists('id', $attributes)) {
unset($attributes['id']);
}
$value = '';
$parent = '';
$label = '';
$inputAttributes = $attributes;
$labelAttributes = $globalLabelAttributes;
$selected = isset($inputAttributes['selected']) && $inputAttributes['type'] != 'radio' && $inputAttributes['selected'] != false ? true : false;
$disabled = isset($inputAttributes['disabled']) && $inputAttributes['disabled'] != false ? true : false;
if (is_scalar($optionSpec)) {
$optionSpec = array(
'label' => $optionSpec,
'value' => $key
);
}
if (isset($optionSpec['value'])) {
$value = $optionSpec['value'];
}
if (isset($optionSpec['parent'])) {
$parent = $optionSpec['parent'];
}
if (isset($optionSpec['label'])) {
$label = $optionSpec['label'];
}
if (isset($optionSpec['selected'])) {
$selected = $optionSpec['selected'];
}
if (isset($optionSpec['disabled'])) {
$disabled = $optionSpec['disabled'];
}
if (isset($optionSpec['label_attributes'])) {
$labelAttributes = (isset($labelAttributes))
? array_merge($labelAttributes, $optionSpec['label_attributes'])
: $optionSpec['label_attributes'];
}
if (isset($optionSpec['attributes'])) {
$inputAttributes = array_merge($inputAttributes, $optionSpec['attributes']);
}
if (in_array($value, $selectedOptions)) {
$selected = true;
}
$inputAttributes['value'] = $value;
$inputAttributes['checked'] = $selected;
$inputAttributes['disabled'] = $disabled;
$input = sprintf(
'<input %s%s',
$this->createAttributesString($inputAttributes),
$closingBracket
);
if (null !== ($translator = $this->getTranslator())) {
$label = $translator->translate(
$label, $this->getTranslatorTextDomain()
);
}
$tag = ($parent != '')? "{#*".$parent."*#}": "";
$label = $escapeHtmlHelper($label);
$labelOpen = $labelHelper->openTag($labelAttributes);
$template = $labelOpen . '%s%s%s' . $labelClose;
switch ($labelPosition) {
case self::LABEL_PREPEND:
$markup = sprintf($template, $label, $input, $tag);
break;
case self::LABEL_APPEND:
default:
$markup = sprintf($template, $input, $label, $tag);
break;
}
$combinedMarkup[] = $markup;
}
return implode($this->getSeparator(), $combinedMarkup);
}
Extended and overwrote Zend\Form\View\Helper\FormRow render method this is the same except for the return of the method:
..... more code .....
$child_of = $element->getAttribute('childOf');
if($child_of != '')
{
return array($child_of => sprintf('<div class="control-group%s">%s</div>', $status_type, $markup));
}
return sprintf('<div class="control-group%s">%s</div>', $status_type, $markup);
And finally extended and overwrote the render method of Zend\Form\View\Helper\FormCollection and changed the element foreach loop, basically overwriting the tags if the element is an array, and therefore has a ChildOf tag. Then a clean up of tags:
foreach ($element->getIterator() as $elementOrFieldset) {
if ($elementOrFieldset instanceof FieldsetInterface) {
$markup .= $fieldsetHelper($elementOrFieldset);
} elseif ($elementOrFieldset instanceof ElementInterface) {
$elementString = $elementHelper($elementOrFieldset);
if(!is_array($elementString))
{
$markup .= $elementString;
}
// is child of another element
else
{
foreach($elementString as $key => $value)
{
$match = "{#*".$key."*#}";
$replacement = $value.$match;
$markup = str_replace($match, $replacement, $markup);
}
}
}
}
$pattern = '/[{#\*]+[a-z0-0A-Z]*[\*#}]+/';
$markup = preg_replace($pattern, '', $markup);
This (although ugly) produces the desired results, also because we are just playing with the rendering, validation and form creation are untouched.
All the best,
Aborgrove

Related

Drupal programatically create a form with a for condition

I'm trying to create a form to create a person, and this person can have some relation with another one (like a wife, husband, child, ..), so I try to create something with a button to add a relation, here is my code :
if (empty($form_state['number_liaisons'])) {
$form_state['number_liaisons'] = 1;
}
$form['info_contact']['liaison'] = array(
"#type" => "fieldset",
"#title" => "Liaisons",
"#attributes" => array("class" => array("center"))
);
for ($i = 1; $i <= $form_state['number_liaisons']; $i++) {
$form['info_contact']['liaison'][$i] = array(
"#type" => "fieldset",
"#title" => "Liaison",
"#attributes" => array("class" => array("center"))
);
$form['info_contact']['liaison'][$i]['contact'] = array(
'#type' => 'textfield',
'#title' => t("Personne a lier"),
'#autocomplete_path' => 'crm/autocomplete_liaison',
);
$form['info_contact']['liaison'][$i]['type'] = array(
'#type' => 'select',
'#title' => t("Type de liaison"),
'#options' => Array('enfant' => 'Enfant', 'conjoint' => 'conjoint'),
'#empty_option' => t('- Choisir un type de liaison -'),
);
}
$form['info_contact']['liaison']['add_item'] = array(
'#type' => 'submit',
'#value' => t('Add liaison'),
'#submit' => array('liaison_add_item'),
'#limit_validation_errors' => array(),
);
if ($form_state['number_liaisons'] > 1) {
$form['info_contact']['liaison'] = array(
'#type' => 'submit',
'#value' => t('Remove liaison'),
'#submit' => array('liaison_remove_item'),
'#limit_validation_errors' => array(),
);
}
And the two methods to add / remove a relation :
function liaison_add_item($form, &$form_state)
{
$form_state['number_liaisons']++;
$form_state['rebuild'] = true;
}
function liaison_remove_item($form, &$form_state)
{
if ($form_state['number_liaisons'] > 1) {
$form_state['number_liaisons']--;
}
$form_state['rebuild'] = true;
}
My problem is that when I click on the "Add liaison" button all the fields disappear, and when I click on the remove liaison the fields came back.
So when I have more than 1 in my $form_state['number_liaisons'] the for doesn't show my fields.
Anyone know how to fix that?
I think it's because the liaisons all share the same field name, the $form_state doesn't really care if they are nested. So try renaming to something like;
["contact_{$i}"] rather than just ['contact']

My ajax Drupal form shows up old values

I have developed ajax drupal and add a form (textfield and button) in it using #ajax key and callback function where I do my process and return my form new element.
So when I starts adding my data form, it works fine for me and form_state['values'] are updated fine.
The problem here is when I reload my form and I add some data, form_state['values'] are not the same in my form fields.
Here is my code:
function my_horoscope_menu() {
$items = array();
$items['admin/horoscopes'] = array(
'title' => 'Horoscopes',
'page callback' => 'drupal_get_form',
'page arguments' => array('my_horoscope_admin_form'),
'access arguments' => array('Administer site configuration '),
//'type' => MENU_LOCAL_TASK,
);
return $items;
}
function my_horoscope_admin_form($form, &$form_state) {
$form['settings']['horoscopes']['add_horoscopes'] = array(
'#type' => 'fieldset',
'#title' => t('Add horoscopes'),
'#collapsible' => TRUE,
'#collaspsed' => TRUE,
);
$form['settings']['horoscopes']['add_horoscopes']['name'] = array(
'#type' => 'textfield',
'#title' => t('Horoscope name'),
'#default_value' => t('Horoscope name'),
'#size' => 20,
'#maxlength' => 60,
'#required' => FALSE,
);
$form['settings']['horoscopes']['add_horoscopes']['beginning_date_rang'] = array(
'#type' => 'date',
'#title' => t('Horoscope beginning date rang'),
'#description' => t('Set the beginning date rang of this horoscope.'),
'#required' => FALSE,
);
$form['settings']['horoscopes']['add_horoscopes']['ending_date_rang'] = array(
'#type' => 'date',
'#title' => t('Horoscope ending date rang'),
'#description' => t('Set the ending date rang of this horoscope.'),
'#required' => FALSE,
);
$form['settings']['horoscopes']['add_horoscopes']['add_button'] = array(
'#type' => 'button',
'#value' => t('Add this horoscope'),
'#submit' => array(''),
'#ajax' => array(
'event' => 'click',
'callback' => 'add_horoscope_ajax_process',
'wrapper' => 'add_horoscope_wrapper',
),
);
$form['settings']['horoscopes']['add_horoscopes']['adding_horoscope_wrapper'] = array(
'#type' => 'markup',
'#prefix' => '<div id="add_horoscope_wrapper">',
'#suffix' => '</div>',
);
return $form;
}
function add_horoscope_ajax_process ($form, &$form_state) {
if (isset($form_state['values'])) {
if (isset($form_state['values']['name']) AND $form_state['values']['name'] != '') {
$name = $form_state['values']['name'];
}
if (isset($form_state['values']['beginning_date_rang']) AND $form_state['values']['beginning_date_rang'] != '') {
$beginning_date_rang = $form_state['values']['beginning_date_rang'];
$beginning_date_rang1 = sprintf("%04d-%02d-%02d", $beginning_date_rang['year'], $beginning_date_rang['month'], $beginning_date_rang['day']);
}
if (isset($form_state['values']['ending_date_rang']) AND $form_state['values']['ending_date_rang'] != '') {
$ending_date_rang = $form_state['values']['ending_date_rang'];
$ending_date_rang1 = sprintf("%04d-%02d-%02d", $ending_date_rang['year'], $ending_date_rang['month'], $ending_date_rang['day']);
}
// Prepare record to add
$record = array(
'h_name' => $name,
'h_date_begin' => $beginning_date_rang1,
'h_date_end' => $ending_date_rang1,
);
// Add the record
$res = drupal_write_record('my_horoscope_structure', $record);
if($res != FALSE) {
drupal_set_message(t('Horoscope #name is inserted successfully!', array('#name' => $name)));
}
}
// return $form
return $form['settings']['horoscopes']['add_horoscopes']['adding_horoscope_wrapper'];
}

How to create multiple form submit buttons with alternate routes in ZF2

In ZF2, how do you create multiple submit buttons that each lead to different routes? In the Forms and actions chaper of the ZF2 tutorial, a form is created with a single submit button with the label “Go” that processes the input data and returns to the index page (route). Where do we put the pertinent scripts if we wanted four buttons:
Save action: saves user input, route: return to current page
Save and Close action: saves user input, route: return to index (Album)
Clear action: no action, route: return to current page
Close action: no action, route: return to index (Album)
I assume the buttons are created like this:
namespace Album\Form;
class AlbumForm extends Form
{
public function __construct($name = null)
{
// ... //
$this->add(array(
'name' => 'savebutton',
'attributes' => array(
'type' => 'submit',
'value' => 'Save',
'id' => 'savebutton',
),
));
$this->add(array(
'name' => 'save_closebutton',
'attributes' => array(
'type' => 'submit',
'value' => 'Save & Close',
'id' => 'save_closebutton',
),
));
$this->add(array(
'name' => 'clearbutton',
'attributes' => array(
'type' => 'submit',
'value' => 'Clear',
'id' => 'clearbutton',
),
));
$this->add(array(
'name' => 'closebutton',
'attributes' => array(
'type' => 'submit',
'value' => 'Close',
'id' => 'closebutton',
),
));
}
}
This is what the edit action looks like with only one submit button:
// module/Album/src/Album/Controller/AlbumController.php:
//...
// Add content to this method:
public function editAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
if (!$id) {
return $this->redirect()->toRoute('album', array(
'action' => 'add'
));
}
$album = $this->getAlbumTable()->getAlbum($id);
$form = new AlbumForm();
$form->bind($album);
$form->get('submit')->setAttribute('value', 'Edit');
$request = $this->getRequest();
if ($request->isPost()) {
$form->setInputFilter($album->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$this->getAlbumTable()->saveAlbum($form->getData());
// Redirect to list of albums
return $this->redirect()->toRoute('album');
}
}
return array(
'id' => $id,
'form' => $form,
);
}
//...
Since pairs of buttons have the same form action and pairs of buttons have the same route, I image we want to add two if statements somewhere here, unless a switch statement is better.
Quick 'n dirty way to do what you need:
public function editAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
if (!$id) {
return $this->redirect()->toRoute('album', array(
'action' => 'add'
));
}
$album = $this->getAlbumTable()->getAlbum($id);
$form = new AlbumForm();
$form->bind($album);
$form->get('submit')->setAttribute('value', 'Edit');
$request = $this->getRequest();
if ($request->isPost()) {
$form->setInputFilter($album->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$input = $form->getData();
if (!empty($input['save_closebutton'])) {
return $this->redirect()->toRoute('album', array(
'controller' => 'AlbumController',
'action' => 'index',
));
}
}
}
return array(
'id' => $id,
'form' => $form,
);
}

How to add a navbarForm to Yiistrap navbar

I'd like to add a login form to my navbar using Yiistrap.
However the documentation is lacking.
Does anyone know how to implement this?
You can extend TbHtml class with something like this:
class HHtml extends TbHtml {
/**
* #param $action
* #param string $method
* #param array $htmlOptions
* #return string
*/
public static function loginForm($action, $method = 'post', $htmlOptions = array()) {
if (isset($htmlOptions['visible']) && !$htmlOptions['visible'])
self::addCssClass('hide', $htmlOptions);
self::addCssClass('navbar-form', $htmlOptions);
$output = self::beginFormTb(self::FORM_LAYOUT_INLINE, $action, $method, $htmlOptions);
$output .= self::textField('UserLogin[username]', '', array('placeholder' => 'Username', 'size' => TbHtml::INPUT_SIZE_SMALL));
$output .= self::passwordField('UserLogin[password]', '', array('placeholder' => 'Password', 'size' => TbHtml::INPUT_SIZE_SMALL));
//$output .= self::checkBox('UserLogin[rememberMe]', false, array('label' => 'Remember me'));
$output .= self::submitButton('Sign in');
$output .= parent::endForm();
return $output;
}
}
So you can use it items section of TbNavbar that way:
array(
'class' => 'bootstrap.widgets.TbNav',
'htmlOptions' => array(
'class' => 'pull-right'
),
'items' =>array(
HHtml::loginForm('/site/login', 'post', array('visible' => Yii::app()->user->isGuest)),
array('label'=>'Logout ('.Yii::app()->user->name.')', 'url'=>array('/site/logout'), 'visible'=>!Yii::app()->user->isGuest),
),
),
<?php $this->widget('bootstrap.widgets.BsNavbar', array(
'collapse' => TRUE,
'color' => BsHtml::NAVBAR_COLOR,
'brandLabel' => BsHtml::icon(BsHtml::GLYPHICON_HOME),
'brandUrl' => Yii::app()->homeUrl,
'items' => array(
array(
'class' => 'bootstrap.widgets.BsNav',
'type' => 'navbar',
'activateParents' => true,
'items' => array(
//other items
),
),
BsHtml::navbarSearchForm(Yii::app()->createUrl('search'), 'get', array(
'class' => 'navbar-form navbar-right',
)),
))); ?>

Zend Framework Custom Validator on Form Elements

Hi I am trying to set up element custom validator in a zend form this is what I have.
class Siteanalysis_Form_User_ChangePassword extends SA_Form_Abstract
{
public function init()
{
// add path to custom validators
$this->addElementPrefixPath(
'Siteanalysis_Validate',
APPLICATION_PATH . '/modules/siteanalysis/models/validate/',
'validate'
);
$this->addElement('text', 'passwdVerify', array(
'filters' => array('StringTrim'),
'validators' => array('PasswordVerification',array('StringLength', true, array(6, 128))),
'decorators' => array('ViewHelper','Errors',
array('HtmlTag', array('id' => 'passwdVerify')),
array('Label', array('placement'=>'prepend','class'=>'label'))),
'required' => true,
'label' => 'Confirmar contraseña nueva',
));
$this->addElement('submit', 'change', array(
'label' => 'Cambiar',
'required' => false,
'ignore' => true,
'decorators' => array('ViewHelper')
));
}
}
class Siteanalysis_Validate_PasswordVerification extends Zend_Validate_Abstract
{
const NOT_MATCH = 'notMatch';
protected $_messageTemplates = array(
self::NOT_MATCH => 'Verifique que las contraseñs sean iguales.'
);
public function isValid($value, $context = null)
{
$value = (string) $value;
$this->_setValue($value);
if (is_array($context)) {
if (isset($context['passwdNew'])
&& ($value == $context['passwdNew']))
{
return true;
}
} elseif (is_string($context) && ($value == $context)) {
return true;
}
$this->_error(self::NOT_MATCH);
return false;
}
}
The problem is that its not calling the PasswordVerification custom validator, does any one see something wrong with it?
Thanks.
Update: Test Setup
$form = new Siteanalysis_Form_User_ChangePassword();
$value = 'Adam';
$data = array('passwdVerify' => $value);
$validation = $form->isValid($data);
if ( $validation === false ) {
$element = $form->getElement('passwdVerify');
$errors = $element->getErrors();
$msg = $element->getMessages();
} else {
$values = $form->getValidValues($data);
}
If $value is
empty I get $errors "isEmpty"
'Adam' I get $errors "noMatch" and "stringLengthTooShort"
'AdamSandler' I get $errors "noMatch"
Your validators array should look like this:
'validators' => array('PasswordVerification' => array('StringLength', true, array(6, 128)))
not
'validators', array('PasswordVerification',array('StringLength', true, array(6, 128))),