I just tried to apply tableselect to a tabular form I had originally created to be able to
selectively modify several fields in some rows. I discovered that all of my
#default_values in the individual table-cell form-elements (select or textfield) are
completely ignored after drupal_render has done its work on the individual cells

Have I missed some important setting?

Thanks

Comments

doobs’s picture

I think I am starting to understand this. In a modules form building function (called by drupal_get_form() )
you would typically build up an array of rows and a header and return the form as:

return array( '#type'=> 'tableselect',
       '#options'=> $myRowsArray,
       '#multiple' => TRUE,
       '#header'=> $myTableHeaderArray,
);

Ordinarily any hierarchy/tree of form data is walked to add form element IDs and names, to the various form elements
and fieldsets, on the assumption that module writers create these array trees using unadorned names.
Unfortunately, because the actual row data is added via the '#' adorned '#options' key, the form processing code
simply ignores the content of $myRowsArray.

My naive attempt to redress the situation by applying drupal_render() to each cell value of each row of $myRowsArray, although
adding html form elements to the tableselect data cells, bypasses the hierarchical processing of setting id, name and default
values :-/

To try and workaround the problem my best guess was replace the above as:

return array( '#type'=> 'tableselect',
       '#theme' => 'mymodule_this_tableselect_form_theme_func',
       'rows'=> $myRowsArray,
       '#multiple' => TRUE,
       '#header'=> $myTableHeaderArray,
       '#options' => $myDummyRowsArray,
);

So now, all the form elements in $myRowsArray are preprocessed in the normal way, to set form 'id's etc
and when it comes time to render this 'tableselect' part of the form, drupal calls my rendering function:

function theme_mymodule_this_tableselect_form_theme_func(&$form) {

  $header = $form['#header'];
  $rows = $form['rows'];
  $formattedRows = array();
  foreach(element_children($rows) as $i){
    $formattedRows[$i] = array(
        'field1' => drupal_render($rows[$i]['field1']),
       ...
    );
  }
  $form['#options'] = $formattedRows;
  unset($form['rows']);
  $output = theme_tableselect($form);
  return $output;
}

I believe this code works perfectly. Unfortunately the returned $output is thrown away and the $form is
rerendered a second time by theme_tableselect using the original empty $myDummyRowsArray.

I'm at a bit of a loss as to how to prevent that.

doobs’s picture

So I added a dpm(debug_backtrace()); call to theme_tableselect() and it comes down to

function drupal_render(&$elements) {
   ...
  if (!isset($elements['#children'])) {
    $children = element_children($elements);
    // Render all the children that use a theme function.
    if (isset($elements['#theme']) && empty($elements['#theme_used'])) {
      $elements['#theme_used'] = TRUE;
      ...
      unset($elements['#prefix'], $elements['#suffix']);
      $content = theme($elements['#theme'], $elements);     // first theming instance
     ...
    }
 }
  if (isset($content) && $content !== '') {
    $elements['#children'] = $content;
  }

  // Until now, we rendered the children, here we render the element itself
  if (!isset($elements['#printed'])) {
    $content = theme(!empty($elements['#type']) ? $elements['#type'] : 'markup', $elements);  // second theming instance
    $elements['#printed'] = TRUE;
  }

Basically, I think I could fix it by returning the tableselect form element in a more deeply nested form with the
'#theme' => 'mymodule_this_tableselect_form_theme_func' located at a higher hierarchic level, or apparently I can just do this:

return array( '#type'=> 'tableselect',
       '#theme' => 'mymodule_this_tableselect_form_theme_func',
       'rows'=> $myRowsArray,
       '#multiple' => TRUE,
       '#header'=> $myTableHeaderArray,
       '#options' => $myDummyRowsArray,
       '#printed' => TRUE,                             // <---  appears to work ...
);

And I don't even have to hack Elements.
Oh, go me!

Now what would be really useful is if I could add

  '#attributes' => array(   'width' => '100%',  'style' => 'font-size: x-small;' )

and get it applied to the underlying theme_table(), when its called ...

Dave Reid’s picture

Status: Active » Closed (won't fix)

As per the definition, table selects are not meant to hold extra form elements. Any change in this element would require it to be changed in D7/D8 first, then backported.