I am having the same issue.
Situation
We have come to think that the separation between CSS and symfony's widget generation is a big learning curve for the integrators and we think there must be a way to leave the integrators more independent from the developers.
Client always want to have custom styled forms... and yet, custom handmade !DRY code is done. What I mean is that, Graphic designers do not design forms with only input text and label, they find all creative ways and our reality is to make it work in the framework to look the same as they design.
That's why we thought of using a sfWidgetFormSchemaFormatter, but form controllers are all different, and we wanted to be able to inject styling FROM the views... because it's an integration matter, not application matter. per se.
Solution path
My team-lead and I came up to a solution. Mostly using partials and looping through them. A form view could look like what follows.
Our solution is not perfect because we'd like to say by the call in the view which controller could get which class names. But it's better than to use CSS's input[type=text] because our client still uses < IE8. But at least,
So we thought of Why not mapping field id's with a className that could have a different behavior (different from label+input (and of course +helper+error if needs be)).
What we do is that we inject control style for those special controllers (e.g.: label+select+select+select+helper+error) let's say this Widget has id some_field_id and we need it to the parent <li>.
We could do it like this:
$node_classes_map => array('some_field_id'=>'date-selector')
So... now's time for some code:
First, we need to use the 'list' widgetFormatter:
<?php
// lib/form/SomeForm.php
class SomeForm extends BaseSomeForm
{
public function configure()
{
$this->widgetSchema->setFormFormatterName('list');
}
}
Or, you could use qais answer to have ALL forms as lists (at last!! I didn't knew before now).
The view:
<?php
// apps/appName/modules/moduleName/templates/someviewSuccess.php
$node_classes_map = array(
'some_field_id'=>'date-selector'
// .date-selector would have many select side by side.
// (no CSS for that in this example)
);
include_partial('global/patterns/formParent',
array(
'form'=>$form,
'form_classes'=>'labels-on-left',
'node_classes_map' => $node_classes_map
)
);
formParent looks like:
<?php
// apps/appName/templates/_patterns/formParent.php
$form_classes_attr = (isset($form_classes))?' class="'.$form_classes.'"':'';
$form_list_node_partial = 'global/patterns/listFormDefaultNode';
$model_name_lowercase = strtolower($form->getModelName());
$node_classes_map = (isset($node_classes_map))?$node_classes_map:array();
?>
<?php use_stylesheets_for_form($form) ?>
<?php use_javascripts_for_form($form); ?>
<form action="<?php
echo url_for($model_name_lowercase.'/'.($form->getObject()->isNew() ? 'create' : 'update').(!$form->getObject()->isNew() ? '?id='.$form->getObject()->getId() : ''))
?>"<?php echo $form_classes_attr;
?> method="post" <?php $form->isMultipart() and print 'enctype="multipart/form-data" '?>>
<h2><?php echo __(($form->getObject()->isNew()?'new.':'edit.').$model_name_lowercase.'.form.title'); ?></h2>
<fieldset>
<ol>
<?php
foreach($form as $item){
$node_classes = (isset($node_classes_map[$item->renderId()]))?$node_classes_map[$item->renderId()]:'';
include_partial($form_list_node_partial,
array(
'item'=>$item,
'node_classes' => $node_classes
)
);
}
?>
<ol>
</fieldset>
<?php echo $form->renderHiddenFields() ?>
<?php echo $form->renderGlobalErrors() ?>
<?php echo (!$form->getObject()->isNew())? '<input type="hidden" name="sf_method" value="put" />':''; ?>
</form>
Each form element gets rendered by either a custom one or listFormDefaultNode.
From there, you could come up with a different partial to render a controller differently from a mainstream label+input+helper+errors to something more complex like label+select+select+select+helper+erros.
This is my default form *Node
<?php
// apps/appName/templates/_patterns/listFormDefaultNode.php
/**
* Manual: http://www.symfony-project.org/forms/1_4/en/A-Widgets
*/
if(!$item->isHidden()){ ?>
<li class="<?php echo $item->renderId().((is_string($node_classes))?' '.$node_classes:''); ?>">
<?php echo $item->renderLabel(); ?>
<?php echo $item; ?>
<?php echo (!!$item->renderHelp())?'<span class="helpers">'.$item->renderHelp().'</span>':''; // I did not find any hasHelper(), so switching to bool ?>
<?php echo ($item->hasError())?'<span class="alertify error">'.$item->renderError().'</span>':''; ?>
</li>
<? }
Lastly, here is a start from my CSS:
Please note, here is a part of a project CSS and this uses part of concepts i'm developping with Snippies. Go there to get more ideas how I am structuring my CSS.
/* ****** Misc ****** */
/**
* General form views settings
**/
form ol, form ul {
list-style-type: none;
padding: 0;
margin: 0;
}
form input.text, form select {
display: block;
border: 0;
padding: 4px;
line-height: 12px;
font-size: 15px;
}
form select {
background-color: #e3e8eb;
color: #506775;
margin: 0;
}
form label {
color: #345569;
display: block;
font-weight: bold;
margin-bottom: 5px;
}
form input.text {
color: #345569;
border: none;
background-color: #e3e8eb;
color: #738996;
border: 0px solid #c3c987;
padding: 4px;
line-height: 12px;
width: 100%;
}
form fieldset {
padding: 0;
margin: 0;
border: none;
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid #98a7af;
padding: 10px 0;
}
form li {
margin-bottom: 10px;
}
form li .helpers {
color: #98a7af;
}
/**
* /General form views settings
**/
/**
* General error messages
**/
.alertify {
padding: 0.8em;
margin-bottom: 1em;
border: 2px solid #ddd;
}
.alertify strong, .alertify p {
padding: 0 0 10px 0;
display: block;
}
.alertify ul {
margin: 0;
padding: 0 0 0 15px;
}
.alertify ul li {
padding: 0 0 5px 0;
}
.error, .alert {
background: #fbe3e4;
color: #8a1f11;
border-color: #fbc2c4;
}
/**
* /General error messages
**/
/* ****** /Misc ****** */
/* ****** Specialization ****** */
/**
* Form labels on the left
*/
form.labels-on-left fieldset {
padding: 0;
margin: 0;
padding-bottom: 10px;
margin-bottom: 10px;
}
form.labels-on-left li {
clear: both;
overflow: hidden;
}
form.labels-on-left li input.text, form.labels-on-left li select {
border: 0;
padding: 4px;
line-height: 12px;
}
form.labels-on-left li input.text {
width: 815px !important;
float: left;
padding: 6px;
color: #506775;
}
form.labels-on-left li select {
background-color: #e3e8eb;
color: #506775;
height: 26px;
width: 400px;
margin: 0;
float: left;
}
form.labels-on-left li .beside {
float: left;
}
form.labels-on-left li .beside a {
font-weight: bold;
padding: 5px 20px;
font-size: 14px;
display: block;
}
form.labels-on-left li label {
color: #345569;
width: 80px;
font-size: 120%;
text-align: right;
font-weight: bold;
float: left;
display: block;
padding: 5px 10px 0 0;
}
form.labels-on-left li .error span, form.labels-on-left li .helpers span {
padding: 5px;
display: block;
}
form.labels-on-left li .helpers {
width: 100%;
}
/**
* /Form labels on the left
*/
/* ****** /Specialization ****** */