I'm fairly new to drupal and still learning FAPI so perhaps I'm misunderstanding how this *should* work.

I have a query I am running where I need the result set ordered in a certain way (alphabetical by last name).

When I run the query I get the correct result - but when I display it in the tableselect element - the result set is mis-ordered.

I spent several hours trying to figure out what was going on. Finally I went into the code and found this:

  // Sort the options by their #weight if they have a #weight.
    uasort($element['#options'], 'element_sort');

the problem is that there is no check for the fact that the weight property might not be there.

When the '#weight' property is absent from all the options this uasort totally messes up queries that have an 'ORDER BY' clause in them.

I was able to work-around the issue by adding in the '#weight' property to the options array elements - but this is an undocumented and unexpected result.

If one wants a table ordered in a certain way WITHOUT giving the end-user the ability to select a sort order - one *should* be able to use an 'ORDER BY' clause in their sql query and have the table select element preserve the query result order.

either a check for the '#weight' property should actually be performed around the above listed code (as the code comment suggests) - or the documentation and README file should document that the #weight property is required to preserve query result order.

Comments

letapjar’s picture

Here is a potential solution to this issue - a check of the '#weight' property is done on all the '#options' elements - if even one of them has a '#weight' property then the uasort is performed - but if none of them have the weight - the uasort is ignored and the ORDER BY of the original query is preserved.

   // Sort the options by their #weight if they have a #weight.
    $has_weight=FALSE;
    foreach($element['#options'] as $option){
     if  (array_key_exists('#weight',$option)){
      $has_weight=TRUE;
     }
     }
     if ($has_weight){
    uasort($element['#options'], 'element_sort');
     }

while this works - it is computationally expensive - looping through every element and doing an array_key_exists could really slow things down for large result sets.

another potential solution would be to add a parameter/property to the table select element such that if set to FALSE the uasort would be skipped.

i.e.

$element['#sort_by_weights'] =>FALSE;

Then the check above could be just:

    // Sort the options by their #weight if we are supposed to.
   if ($element['#sort_by_weights']){
      uasort($element['#options'], 'element_sort');
     }

This would require a default setting and I'm not sure whether the default should be TRUE or FALSE - my leaning is towards FALSE so that the default behavior is to preserve SQL result orders.

letapjar’s picture

OK so here is the second proposed solution from the post above in the form of a patch.
Apologies for the multiple posts - still learning about how to create patches etc.

--- elements.module 2009-12-02 06:22:11.000000000 -0500
+++ elements2.module 2010-03-23 14:17:28.000000000 -0400
@@ -9,6 +9,7 @@ function elements_elements() {
'#input' => TRUE,
'#js_select' => TRUE,
'#multiple' => TRUE,
+ '#sort_by_weights'=>FALSE,
'#process' => array('form_process_tableselect'),
'#options' => array(),
'#empty' => '',
@@ -78,9 +79,11 @@ function form_process_tableselect($eleme
$element['#default_value'] = array();
}

- // Sort the options by their #weight if they have a #weight.
- uasort($element['#options'], 'element_sort');
-
+ // Sort the options by their #weight if we are supposed to.
+
+ if ($element['#sort_by_weights']){
+ uasort($element['#options'], 'element_sort');
+ }
// Create a checkbox or radio for each item in #options in such a way that
// the value of the tableselect element behaves as if it had been of type
// checkboxes or radios.

letapjar’s picture

Status: Active » Needs review

changin status to needs review for the proposed patch

dave reid’s picture

Status: Needs review » Needs work

element_sort() does have a check if items don't have a #weight value. Is this something that needs to be fixed in core?

dave reid’s picture

Status: Needs work » Closed (won't fix)

Yeah, this is just something core will have to fix.

If I do this:

$elements = array(
  array(
    '#title' => 'AAA',
    '#weight' => 1,
  ),
  array(
    '#title' => 'BBB',
    '#weight' => 1,
  ),
  array(
    '#title' => 'CCC',
    '#weight' => 1,
  ),
);
uasort($elements, 'element_sort');
var_export($elements);

I get:

Array
(
    [2] => Array
        (
            [#title] => CCC
            [#weight] => 1
        )

    [1] => Array
        (
            [#title] => BBB
            [#weight] => 1
        )

    [0] => Array
        (
            [#title] => AAA
            [#weight] => 1
        )

)

Which re-ordered the elements even though they had the same weight.

dave reid’s picture

Status: Closed (won't fix) » Needs review

Actually, the problem is that form_builder() hasn't run yet on the form when we call element_sort(). The form_builder() assigns a default weight for fields to preserve array ordering, like we want. If we perform the element_sort in the theme function, we got the proper ordering.

dave reid’s picture

HAH. Tablesorts are broken in D7 because of this as well.

dave reid’s picture

dave reid’s picture

Status: Needs review » Postponed

This will wait until a D7 fix then to backport.

dave reid’s picture

Status: Postponed » Fixed

Tested the core patch and backported it. Worked for me with multiple cases. Committed the fix to CVS. Please go review the core patch now!
#700380: Regression: Tableselect sorting is altered if '#weight' is not set
http://drupal.org/cvs?commit=345862

letapjar’s picture

so we can just eliminate the uasort line? - cool.

just tested on my use case - the patch works as expected.

Thanks for jumping on this - it was really driving me nuts on a list that had to have the right order.

jerome72’s picture

Thank you so much for the tip! I was going mad with the problem of order, and removing the line did the trick!

I just commented it:

  // Sort the options by their #weight if they have a #weight.
  //  uasort($element['#options'], 'element_sort');

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.