Is passing SelectListItems via ViewData preventing the selected value from being recognised? - html.dropdownlistfor

I had this DropDownListFor() in a view and it worked as expected:
View
#Html.EditorFor(m => m.Item.Starsign)
#Html.EditorFor(m => m.Item.Title)
#Html.EditorFor(m => m.Item.Name)
#Html.DropDownListFor(m => m.Item.ShoeID,
Model.ShoeOptions, htmlAttributes: new { #class = "form-control" })
Result
<option value="1">Nike Trainers</option>
<option value="2">Slippers</option>
<option selected="selected" value="3">Western Boots</option>
<option value="4">Asics Runner</option>
Then I moved it into a separate EditorTemplate, and the current value of the ShoeID property is no longer selected.
View
#Html.EditorFor(m => m.Item.Starsign)
#Html.EditorFor(m => m.Item.Title)
#Html.EditorFor(m => m.Item.Name)
#Html.EditorFor(m => m.Item.ShoeID,
new { options = Model.ShoeOptions })
EditorTemplate
#model int
#{
List<SelectListItem> options = ViewData["options"] as List<SelectListItem>;
}
#Html.DropDownListFor(m => m, options, htmlAttributes: new { #class = "form-control" })
Result
<option value="1">Nike Trainers</option>
<option value="2">Slippers</option>
<option value="3">Western Boots</option><!-- this should be selected, but isn't -->
<option value="4">Asics Runner</option>
It appears that passing the List<SelectListItem> via ViewData prevents it from matching the ShoeID property with the correct item from the list of options.
Is that what's happening or am I missing something?

Related

Using foolproof and requiredif to validate a string field

I have the following fields in my data model:
public bool JointAccount { get; set; }
[RequiredIf("JointAccount", "true", ErrorMessage = "Please select a Title")]
public string JointAccountTitle { get; set; }
[RequiredIf("JointAccount", "true", ErrorMessage = "Please enter first name")]
public string JointAccountFirstName { get; set; }
I have the following in my views:
<div class="form-group">
#Html.Label("Joint Account?", htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-8">
<div class="checkbox">
#Html.EditorFor(model => model.JointAccount)
#Html.ValidationMessageFor(model => model.JointAccount, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
#Html.Label("Title", htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-8">
<select required style="width:100%;height:35px;border-radius:4px;padding-left:10px;" id="JointAccountTitle" name="JointAccountTitle" class="form-control input required">
<option value="">Please Select Title</option>
<option value="Mr">Mr</option>
<option value="Ms">Ms</option>
<option value="Miss">Miss</option>
<option value="Mrs">Mrs</option>
<option value="Fr">Fr</option>
<option value="Dr">Dr</option>
<option value="Prof">Prof</option>
<option value="Rev">Rev</option>
<option value="Sr">Sr</option>
<option value="Br">Br</option>
</select>
#Html.ValidationMessageFor(model => model.JointAccountTitle, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.Label("First Name", htmlAttributes: new { #class = "control-label col-md-4" })
<div class="col-md-8">
#Html.EditorFor(model => model.JointAccountFirstName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.JointAccountFirstName, "", new { #class = "text-danger" })
</div>
</div>
I am trying to ensure that data is entered here if the jointaccount checkbox is filled but it does not seem to be throwing any validation error on the textbox only on the dropdown list for the title, any ideas here?
Your creating the <select> element manually and not adding the necessary data-val-* attributes required by jquery.validate.unobtrusive.js to add the rules for to jquery.validate.js so you could never get jquery validation for the <select> (note that the required attribute is HTML5 validation, not jquery validation).
If your claiming that your getting validation on the JointAccountTitle property (dropdownlist), but not on the JointAccountFirstName (textbox), it means that jquery client side validation is not even being triggered. The most likely cause is that you do not have the correct scripts loaded, or they are loaded in the wrong order. You need to have
jquery-{version}.js
jquery.validate.js
jquery.validate.unobtrusive.js
mvcfoolproof.unobtrusive.js
The to get jquery validation for the select list, you need to add the relevant data-val- attributes to you manual html, or better, generate you dropdownlist using the DropDownListFor() method
#Html.DropDownListFor(m => m.JointAccountTitle, Model TitleList, "Please select title", new { # class="form-control input })
where TitleList is an IEnumerable<SelectListItem> property in your view model containing the values for the options
I recently ran into the same issue. Try replacing the .EditorFor() with a .TextBoxFor():
#Html.TextBoxFor(model => model.JointAccountFirstName, new { htmlAttributes = new { #class = "form-control" } })
Not sure why this is a problem for Foolproof, but it worked for me.

Nested form state and one onChange function in React

I'm working on a form in React. I have nested state in MatchForm component and "bind" values from state with different inputs. I would like to have on onChange function which manages all cinput changes and pass it to state. Current onChange function works when I have only lineup inputs. But when I added other inputs I have no idea how to handle with it. How should I name inputs and how onChange function should look like to work with all inputs (without many ifs)?
Thank you in advance
class MatchForm extends React.Component {
constructor(props) {
super(props);
this.state = {
lineup: {
setter: '',
receiver1: '',
receiver2: '',
attacker: '',
blocker1: '',
blocker2: '',
libero: ''
},
distribution: {
receiver1: 20,
receiver2: 20,
attacker: 20,
blocker1: 20,
blocker2: 20
},
risk: 'normal',
default: false
};
this.onChange = this.onChange.bind(this);
}
onChange(e) {
this.setState({lineup: { ...this.state.lineup, [e.target.name]:e.target.value } })
}
render() {
return (
<div>
{
Object.keys(this.state.lineup).map((el,i) =>
<select
key={i}
name={el}
onChange={this.onChange}
value={this.state.lineup[el]}
>
<option value="" disabled>{el}</option>
<option value="Marian Noga">Marian Noga</option>
<option value="Janek Kowalski">Janek Kowalski</option>
</select>
)
}
{
Object.keys(this.state.distribution).map((el,i) =>
<Field
key={i}
field={el}
label={el}
type='number'
value={this.state.distribution[el]}
onChange={this.onChange} />
)
}
<select
name="risk"
onChange={this.onChange}
value={this.state.risk}
>
<option value="" disabled>risk</option>
<option value="safe">safe</option>
<option value="normal">normal</option>
<option value="risk">risk</option>
</select>
<label><input name="default" type="checkbox" value={this.state.default} onChange={this.onChange} /> Set as default lineup</label>
</div>
)
}
}
export default MatchForm
You can bind the onChange function with different values for each of your fields:
onChange={this.onChange.bind(this, 'lineup', el)}
Then if you write your onChange like this:
onChange(field, name, e) {
this.setState({[field]: { ...this.state[field], [name]:e.target.value } });
}
You should be able now to repeat that for each of your fields.
If it is just a controlled input, and no other UI logic, I do something as simple as this:
<input type="text"
name="email"
value={this.state.email}
onChange={e => this.setState({ email: e.target.value })}
/>
You could also try _.set for more complex use cases. Good thing is, the set function alone can be downloaded as an npm package
<input type="text" name="lineup.receiver1" onChange={handleChange} value={this.state.lineup.receiver1} />
import update from "lodash.set";
const handleChange = (ev) => { update(this.state, ev.target.name, ev.target.value ) }
_.set has more complex options for updating a nested object and can be used in our case with HTML name property of a field.

Playframework scala helper for select input with optgroup

Is in playframework scala helper to achive optgroup in input select?
Example:
http://jsfiddle.net/g4ffzvs8/
Code:
<select style="width:300px" id="source">
<optgroup label="Alaskan/Hawaiian Time Zone">
<option value="AK">Alaska</option>
<option value="HI">Hawaii</option>
</optgroup>
<optgroup label="Pacific Time Zone">
<option value="CA">California</option>
<option value="NV">Nevada</option>
<option value="OR">Oregon</option>
<option value="WA">Washington</option>
</optgroup>
</select>
None of the answers satisfied me. Created a new version of the select helper that accepts group names:
#(field: play.api.data.Field, options: Seq[(String, Seq[(String,String)])], args: (Symbol,Any)*)(implicit handler: helper.FieldConstructor, messages: play.api.i18n.Messages)
#helper.input(field, args:_*) { (id, name, value, htmlArgs) =>
#defining( if( htmlArgs.contains('multiple) ) "%s[]".format(name) else name ) { selectName =>
#defining( field.indexes.nonEmpty && htmlArgs.contains('multiple) match {
case true => field.indexes.map( i => field("[%s]".format(i)).value ).flatten.toSet
case _ => field.value.toSet
}){ selectedValues =>
<select id="#id" name="#selectName" #toHtmlArgs(htmlArgs)>
#args.toMap.get('_default).map { defaultValue =>
<option class="blank" value="">#defaultValue</option>
}
#options.map { case (groupName, elements) =>
<optgroup label="#groupName">
#elements.map { case (k, v) =>
#defining( selectedValues.contains(k) ) { selected =>
#defining( args.toMap.get('_disabled).exists { case s: Seq[String] => s.contains(k) }){ disabled =>
<option value="#k"#if(selected){ selected="selected"}#if(disabled){ disabled}>#v</option>
}
}
</optgroup>
}}
</select>
}}
}
(Use lang : play.api.i18n.Lang instead of messages: play.api.i18n.Messages for Play versions prior to 2.4).
You can use this helper this way:
#groupSelect(
form("brand"),
Seq(
"Swedish Cars" -> Seq(
"volvo" -> "Volvo",
"saab" -> "Saab"
),
"German Cars" -> Seq(
"mercedes" -> "Mercedes",
"audi" -> "Audi"
)
)
)
And it will render this:
<select id="brand" name="brand">
<optgroup label="Swedish Cars">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
</optgroup>
<optgroup label="German Cars">
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</optgroup>
</select>
Here is my working solution:
<div class="clearfix #if(MedicalIncidentForm("incidentType.id").hasErrors) {error}">
<label class="note note-title note-#if(MedicalIncidentForm("incidentType.id").hasErrors) {error} else {success}" for="incidentType_id">Rodzaj</label>
<div class="input note note-footer note-#if(MedicalIncidentForm("incidentType.id").hasErrors) {error} else {success}">
<select name="incidentType.id" onchange="checkSelectedValue(this)">
<option></option>
#for(opt <- new IncidentType().getOptionGroups()) {
<optgroup label="#opt._1">
#for(op <- opt._2) {
#**
<option value="#op._1">#op._2</option>
**#
<option value="#op._1" #if(MedicalIncidentForm("incidentType.id").value() == op._1) {selected="selected"}>#op._2</option>
}
</optgroup>
}
</select>
</div>
</div>
Look at the select helper in scala : https://github.com/playframework/playframework/blob/master/framework/src/play/src/main/scala/views/helper/select.scala.html
There is no mention of optgroup so I think the answer is no. Anyway, you can create a helper of your own by copying the code and customize it so that it is integrating the optgroups. For example this is my code for my custom input text. I had to removw implicit i18n language because of a bug :
#(field: play.api.data.Field, args: (Symbol,Any)*)(implicit handler: views.html.helper.FieldConstructor)
#inputType = #{ args.toMap.get('type).map(_.toString).getOrElse("text") }
#inputClass = #{ args.toMap.get('class).map(_.toString) }
#helper.input(field, args.filter(_._1 != 'type).filter(_._1 != 'class):_*) { (id, name, value, htmlArgs) =>
<input type="#inputType" id="#id" class="form-control #inputClass" name="#name" value="#value" #toHtmlArgs(htmlArgs)/>
}

How can I add a custom attribute to a popup_menu using CGI.pm?

I use the following Perl code to generate an HTML popup menu with CGI.pm:
$html->popup_menu(
-name => "to",
-values => [#TO, $param_to],
-labels => {%TO, $param_to => $param_to,},
-default => $param_to,
-onchange => $onchange,
-class => "form-control"
);
The generated menu looks like this:
<select name="to" onchange="if (this.value=='support#abc.com' || document.theForm.supportform.value==1) document.theForm.submit();" class="form-control">
<option value=""> select recipient </option>
<option value="sales#abc.com">Sales Inquiry</option>
<option value="support#abc.com">Technical Support</option>
<option value="jobs#abc.com">Jobs # abc</option>
<option value="investor-relations#abc.com">Investor Relations</option>
<option value="webmaster#abc.com">abc Webmaster</option>
</select>
How can I add the attribute required to the <select> element?
According to the CGI documentation:
Many routines will do something useful with a named argument that it doesn't recognize.
So just add another named argument -required:
$html->popup_menu(
-name => "to",
-values => [#TO, $param_to],
-labels => {%TO, $param_to => $param_to,},
-default => $param_to,
-onchange => $onchange,
-class => "form-control",
-required => "required"
);
This will generate something like:
<select name="to" ... required="required">
However, generating HTML with CGI.pm is a pain and not very maintainable. It's better to use a templating library like Template Toolkit. Templates allow you to separate your Perl code and your HTML (mostly), so you can have something like this:
popup.tt
<select name="to" onchange="if (this.value=='support#abc.com' || document.theForm.supportform.value==1) document.theForm.submit();" class="form-control">
[% FOR option IN options %]
<option value="[% option.value %]">[% option.text %]</option>
[% END %]
</select>
my_script.cgi
use strict;
use warnings;
use CGI;
use Template;
my $tt = Template->new or die Template->error;
my $q = CGI->new;
print $q->header;
my $options = [
{ value => '', text => 'select recipient' },
{ value => 'sales#abc.com', text => 'Sales Inquiry' },
{ value => 'support#abc.com', text => 'Technical Support' },
{ value => 'jobs#abc.com', text => 'Jobs # abc' },
{ value => 'investor-relations#abc.com', text => 'Investor Relations' },
{ value => 'webmaster#abc.com', text => 'abc Webmaster' }
];
$tt->process('foo.tt', { options => $options }) or die $tt->error;
Output
<select name="to" onchange="if (this.value=='support#abc.com' || document.theForm.supportform.value==1) document.theForm.submit();" class="form-control">
<option value="">select recipient</option>
<option value="sales#abc.com">Sales Inquiry</option>
<option value="support#abc.com">Technical Support</option>
<option value="jobs#abc.com">Jobs # abc</option>
<option value="investor-relations#abc.com">Investor Relations</option>
<option value="webmaster#abc.com">abc Webmaster</option>
</select>

Use zend-decorator to format Zend_Form_Element_Radio in a table column with oher Zend_Form_Elements in rows

I want use decorators to format as table the following Zend_Form, placing a description in the first column and the Zend_Form_Element_Radio's options in second column and add 2 select in every row as you can see in the html example later.
I need a concrete/working example.
FORM
class My_Form extends Zend_Form
{
const KIND_1 = 'dineer1';
const KIND_2 = 'dineer2';
const KIND_3 = 'dineer3';
const KIND_4 = 'dineer4';
const KIND_5 = 'dineer5';
const KIND_6 = 'dineer6';
public static $KINDS = array(
1 => self::KIND_1,
2 => self::KIND_2,
3 => self::KIND_3,
4 => self::KIND_4,
5 => self::KIND_5,
6 => self::KIND_6,
);
const DRINK_C = 'c';
const DRINK_M = 'm';
const DRINK_W = 'w';
public static $DRINKS = array(
self::DRINK_C => "cole",
self::DRINK_M => "milk",
self::DRINK_W => "water",
);
const FOOD_B = 'b';
const FOOD_F = 'f';
const FOOD_M = 'm';
const FOOD_P = 'p';
const FOOD_V = 'v';
const FOOD_W = 'w';
public static $FOODS = array(
self::FOOD_B => "burger",
self::FOOD_F => "fruit",
self::FOOD_M => "Meat",
self::FOOD_P => "pizza",
self::FOOD_V => "vegetables",
self::FOOD_W => "Wursterl",
);
public function init()
{
$_please_select = array("" => " please select ");
$this->setMethod(Zend_Form::METHOD_POST);
$input_lunch = new Zend_Form_Element_Radio('lunch');
$input_lunch ->setMultiOptions(self::$KINDS) ;
$this->addElement($input_lunch );
foreach (self::$KINDS as $k => $_descriprion) {
$input_drink = new Zend_Form_Element_Select('drink_' . $k);
$input_drink->addMultiOptions(self::$DRINKS);
$input_food = new Zend_Form_Element_Select('food_' . $k);
$input_food->addMultiOptions($_please_select)
->addMultiOptions(self::$FOODS);
$this->addElement($input_drink);
$this->addElement($input_food);
}
}
}
expected HTML
<html>
<body>
<form action="/" method="POST">
<table>
<thead>
<tr>
<th></td>
<th>kind</td>
<th>drink</td>
<th>food</td>
</tr>
</thead>
<tbody>
<tr>
<td>Description row 1</td>
<td><input type="radio" name="lunch" value "dinner1"></td>
<td>
<select name="drink_1">
<option value="w">Water</option>
<option value="m">Milk</option>
<option value="b">Beer</option>
</select>
</td>
<td>
<select name="food_1">
<option value="">please select</option>
<option value="b">Burger</option>
<option value="f">Fruit</option>
<option value="m">Meat</option>
<option value="p">Pizza</option>
<option value="v">Vegetable</option>
<option value="w">Wurstel</option>
</select>
</td>
</tr>
<tr>
<td>Description row 2</td>
<td><input type="radio" name="lunch" value "dinner2"></td>
<td>
<select name="drink_2">
<option value="w">Water</option>
<option value="m">Milk</option>
<option value="b">Beer</option>
</select>
</td>
<td>
<select name="food_2">
<option value="">please select</option>
<option value="b">Burger</option>
<option value="f">Fruit</option>
<option value="m">Meat</option>
<option value="p">Pizza</option>
<option value="v">Vegetable</option>
<option value="w">Wurstel</option>
</select>
</td>
</tr>
<tr>
<td>Description row 3</td>
<td><input type="radio" name="lunch" value "dinner3"></td>
<td>
<select name="drink_3">
<option value="w">Water</option>
<option value="m">Milk</option>
<option value="b">Beer</option>
</select>
</td>
<td>
<select name="food_3">
<option value="">please select</option>
<option value="b">Burger</option>
<option value="f">Fruit</option>
<option value="m">Meat</option>
<option value="p">Pizza</option>
<option value="v">Vegetable</option>
<option value="w">Wurstel</option>
</select>
</td>
</tr>
<tr>
<td>Description row 4</td>
<td><input type="radio" name="lunch" value "dinner4"></td>
<td>
<select name="drink_4">
<option value="w">Water</option>
<option value="m">Milk</option>
<option value="b">Beer</option>
</select>
</td>
<td>
<select name="food_4">
<option value="">please select</option>
<option value="b">Burger</option>
<option value="f">Fruit</option>
<option value="m">Meat</option>
<option value="p">Pizza</option>
<option value="v">Vegetable</option>
<option value="w">Wurstel</option>
</select>
</td>
</tr>
<tr>
<td>Description row 5</td>
<td><input type="radio" name="lunch" value "dinner5"></td>
<td>
<select name="drink_5">
<option value="w">Water</option>
<option value="m">Milk</option>
<option value="b">Beer</option>
</select>
</td>
<td>
<select name="food_5">
<option value="">please select</option>
<option value="b">Burger</option>
<option value="f">Fruit</option>
<option value="m">Meat</option>
<option value="p">Pizza</option>
<option value="v">Vegetable</option>
<option value="w">Wurstel</option>
</select>
</td>
</tr>
<tr>
<td>Description row 6</td>
<td><input type="radio" name="lunch" value "dinner6"></td>
<td>
<select name="drink_6">
<option value="w">Water</option>
<option value="m">Milk</option>
<option value="b">Beer</option>
</select>
</td>
<td>
<select name="food_6">
<option value="">please select</option>
<option value="b">Burger</option>
<option value="f">Fruit</option>
<option value="m">Meat</option>
<option value="p">Pizza</option>
<option value="v">Vegetable</option>
<option value="w">Wurstel</option>
</select>
</td>
</tr>
</tbody>
<table>
</form>
</body>
</html>
I would suggest a solution based on this answer here https://stackoverflow.com/a/8451723/212940
Your form:-
class My_Form extends Zend_Form
{
const KIND_1 = 'dineer1';
const KIND_2 = 'dineer2';
const KIND_3 = 'dineer3';
const KIND_4 = 'dineer4';
const KIND_5 = 'dineer5';
const KIND_6 = 'dineer6';
public static $KINDS = array(
1 => self::KIND_1,
2 => self::KIND_2,
3 => self::KIND_3,
4 => self::KIND_4,
5 => self::KIND_5,
6 => self::KIND_6,
);
const DRINK_C = 'c';
const DRINK_M = 'm';
const DRINK_W = 'w';
public static $DRINKS = array(
self::DRINK_C => "cole",
self::DRINK_M => "milk",
self::DRINK_W => "water",
);
const FOOD_B = 'b';
const FOOD_F = 'f';
const FOOD_M = 'm';
const FOOD_P = 'p';
const FOOD_V = 'v';
const FOOD_W = 'w';
public static $FOODS = array(
self::FOOD_B => "burger",
self::FOOD_F => "fruit",
self::FOOD_M => "Meat",
self::FOOD_P => "pizza",
self::FOOD_V => "vegetables",
self::FOOD_W => "Wursterl",
);
public function init()
{
$this->addDecorators(
array(
array('ViewScript', array('viewScript' => 'forms/_form_test.phtml'))
)
); //added as part of answer. Note all default decorators are still available.
$_please_select = array("" => " please select ");
$this->setMethod(Zend_Form::METHOD_POST);
$input_lunch = new Zend_Form_Element_Radio('lunch');
$input_lunch ->setMultiOptions(self::$KINDS) ;
$this->addElement($input_lunch );
foreach (self::$KINDS as $k => $_descriprion) {
$input_drink = new Zend_Form_Element_Select('drink_' . $k);
$input_drink->addMultiOptions(self::$DRINKS);
$input_food = new Zend_Form_Element_Select('food_' . $k);
$input_food->addMultiOptions($_please_select)
->addMultiOptions(self::$FOODS);
$this->addElement($input_drink);
$this->addElement($input_food);
}
$this->setElementDecorators(array('ViewHelper'));//added as part of answer
}
}
As you can see it requires only two small changes.
Then you need to create the file scripts/forms/_form_test.phtml which contains:-
<form
id='contact'
action='<?php echo $this->element->getAction(); ?>'
method='<?php echo $this->element->getMethod(); ?>'
>
<table>
<thead>
<tr>
<th></td>
<th>kind</td>
<th>drink</td>
<th>food</td>
</tr>
</thead>
<tbody>
<?php
$elements = $this->element->getElements();
$options = $this->element->lunch->getMultiOptions();
foreach($options as $key => $option){
echo "<tr>\n";
echo "<td>Description row $key</td>\n";
echo "<td><input type='radio' name='lunch' value='$option'</td>\n";
echo "<td>{$elements['drink_' . $key]}</td>\n";
echo "<td>{$elements['food_' . $key]}</td>\n";
echo "</tr>\n";
}
?>
</tbody>
<table>
</form>
The file _form_test.phtml is effectively your decorator for rendering the form. This is a very flexible way of using Zend_Form and I learnt how to do this by reading this article here
The default decorators, such as 'error' should still be available with this method.
On my system I got the exact html output you asked for. Try it and see.
This might not be the prefect solution but it might help you!
class My_Form extends Zend_Form
{
const KIND_1 = 'dineer1';
const KIND_2 = 'dineer2';
const KIND_3 = 'dineer3';
const KIND_4 = 'dineer4';
const KIND_5 = 'dineer5';
const KIND_6 = 'dineer6';
public static $KINDS = array(
1 => self::KIND_1,
2 => self::KIND_2,
3 => self::KIND_3,
4 => self::KIND_4,
5 => self::KIND_5,
6 => self::KIND_6,
);
const DRINK_C = 'c';
const DRINK_M = 'm';
const DRINK_W = 'w';
public static $DRINKS = array(
self::DRINK_C => "cole",
self::DRINK_M => "milk",
self::DRINK_W => "water",
);
const FOOD_B = 'b';
const FOOD_F = 'f';
const FOOD_M = 'm';
const FOOD_P = 'p';
const FOOD_V = 'v';
const FOOD_W = 'w';
public static $FOODS = array(
self::FOOD_B => "burger",
self::FOOD_F => "fruit",
self::FOOD_M => "Meat",
self::FOOD_P => "pizza",
self::FOOD_V => "vegetables",
self::FOOD_W => "Wursterl",
);
public function init()
{
$_please_select = array("" => " please select ");
$this->setMethod(Zend_Form::METHOD_POST);
$this->setDisableLoadDefaultDecorators(true);
$countRows = count(self::$KINDS)+1;
foreach (self::$KINDS as $k => $_descriprion) {
$rowForm = new Zend_Form_SubForm();
$input_lunch = new Zend_Form_Element_Radio('lunch',
array('disableLoadDefaultDecorators' => true,
'label' => 'kind',
'label_placement' => 'prepend'));
$input_lunch ->setMultiOptions(self::$KINDS) ;
$input_lunch->addDecorators(array(
array('ViewHelper'),
array('Label',
array('style' => 'display:block;font-weight:bold', // just for this example
'placement'=>'PREPEND',
'tag'=>'span', //if you want to use other tag
'disableFor'=>true)
),
array(array('data' => 'HtmlTag'), array('tag' => 'td', 'rowspan' => $countRows )),
));
$this->addElement($input_lunch);
// add label just for the first element
$drinkLabel = array('disableLoadDefaultDecorators' =>true);
$drinkDecorators = array(
array('ViewHelper'),
array(array('data' => 'HtmlTag'), array('tag' => 'td')),
);
if ($k == 1) {
$drinkLabel['label'] = 'drink';
$drinkDecorators = array(
array('ViewHelper'),
array('Label',
array('style' => 'display:block;font-weight:bold',
'placement'=>'PREPEND',
'tag'=>'span', //if you want to use other tag
'disableFor'=>true)
),
array(array('data' => 'HtmlTag'), array('tag' => 'td')),
);
}
$input_drink = new Zend_Form_Element_Select('drink', $drinkLabel);
$input_drink->addMultiOptions(self::$DRINKS);
$input_drink->addDecorators($drinkDecorators);
$input_food = new Zend_Form_Element_Select('food', $drinkLabel);
$input_food->addMultiOptions($_please_select)
->addMultiOptions(self::$FOODS);
$input_food->addDecorators($drinkDecorators);
$rowForm->addElement($input_drink);
$rowForm->addElement($input_food);
$rowForm->setSubFormDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'tr')),
));
$rowForm->setDisableLoadDefaultDecorators(true);
$this->addSubForm($rowForm, 'row_' . $k);
}
$this->setSubFormDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'tr')),
));
$this->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'table')),
'Form'
));
}
}
Also, here are some resources to work with table and Zend_Form
http://www.packtpub.com/article/create-shopping-cart-using-zend-framework-2
http://davidcaylor.com/2008/03/24/building-table-based-forms-in-zend_form/
http://blog.kosev.net/2010/06/tutorial-create-zend-framework-form.html
you do not can to do what you want accomplish with a view helper, in my view!
you should make a custom form printing, such as:
<form action="/" method="POST">
<table>
<thead>
<tr>
<th></th>
<th>kind</th>
<th>drink</th>
<th>food</th>
</tr>
</thead>
<tbody>
<?for ($i = 1; $i <= 6; $i++) : ?>
<?
$drink = 'drink_' . $i;
$food = 'food_' . $i;
?>
<tr>
<td>Description row <?=$i?></td>
<td>
<?=$this->form->$drink?>
</td>
<td>
<?=$this->form->$food?>
</td>
</tr>
<? endfor;?>
</tbody>
</table>
</form>
lunch for the field, it is impossible to use it as you ask, you should think of another strategy. At the moment I can think of!
Well here my version of the answer.
Into the init I've add a custom decorator RadioTable to the form, and prefix path to load the decorator
then I decorate all the elements as a normal table, this's important because all my efforts are to keep the facilities offer by standard decorators like "Error"
Please note the Custom_Form_Element_FirstSelect and the ->setSeparator in the radio element
I also add the submit button
public function init()
{
$_please_select = array("" => " please select ");
// I add a my custom decorator
$this->addPrefixPath('Custom_Form_Decorator',
'Custom/Form/Decorator',
'decorator');
$this->setMethod(Zend_Form::METHOD_POST);
$input_lunch = new Zend_Form_Element_Radio('lunch');
$input_lunch ->setMultiOptions(self::$KINDS)
->setSeparator("\t__RADIO_SEPARATOR__\t") //set custom separator I'll use in custom decorator
->setDecorators(array(
'ViewHelper', // add a standard decorator
'Label', // I'll use the label decorator to show text
));
$this->addElement($input_lunch );
foreach (self::$KINDS as $k => $_description) {
// to "mark" the first select I extend the Zend_Form_Element_Select
// with Custom_Form_Element_FirstSelect
$input_drink = new Custom_Form_Element_FirstSelect('drink_' . $k);
$input_drink->addMultiOptions(self::$DRINKS)
->setDecorators(array(
'ViewHelper', // add a standard decorator
array(array('data' => 'HtmlTag'),array('tag' => 'td')), // add a standard decorator
));
$input_food = new Zend_Form_Element_Select('food_' . $k);
$input_food->addMultiOptions($_please_select)
->addMultiOptions(self::$FOODS)
->setDecorators(array(
'ViewHelper',
array(array('data' => 'HtmlTag'),array('tag' => 'td')),
));
$this->addElement($input_drink);
$this->addElement($input_food);
}
// add a the submit button
$submit = new Zend_Form_Element_Submit('submit');
$submit->setLabel('SUBMIT');
$submit->setDecorators(array(
'ViewHelper',
'Errors',
array(array('data' => 'HtmlTag'), array('tag' => 'td', )),
array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));
$this->addElement($submit);
// add the custom decorators to the whole form
$this->setDecorators(array(
'RadioTable',
array('HtmlTag', array('tag' => 'table', )),
'Form'
));
}
the Custom_Form_Element_FirstSelect
class Custom_Form_Element_FirstSelect extends Zend_Form_Element_Select{
}
the Custom_Form_Decorator_RadioTable
class Custom_Form_Decorator_RadioTable extends Zend_Form_Decorator_Abstract {
public function render($content){
$wrap = '';
// I'll take the element
$radioElement = $this->getElement()->getElement('lunch');
// then I render it and explode in array using the separator
$arrayRadio = explode("\t__RADIO_SEPARATOR__\t", $radioElement->__toString());
$count = 0;
$arrayElement = $this->getElement()->getElements();
// loop on all form elements and I render them
foreach ($arrayElement as $keyForm => $element){
// I skip the radio element
if($element instanceof Zend_Form_Element_Radio){
continue;
}
if($element instanceof Custom_Form_Element_FirstSelect ){
// when I found the custom select element I'll prefix with "open-row-tag" and radio button in column
$wrap .= "<tr>". "<td>". $arrayRadio[$count++] . "</td>" . $element->__toString();
// note that the select elements are already decorated
} else if($element instanceof Zend_Form_Element_Select){
// after the last select I close the row
$wrap .= $element->__toString() ."</tr>";
}
if($element instanceof Zend_Form_Element_Submit){
// add the SUBMIT button
$wrap .= $element->__toString() ;
}
}
return $content.$wrap;
}
}
Of course I can use the name of the element instead of use a custom element class to open the table row, but in this way I'm feel more flexible.