Opened 14 years ago

Last modified 15 months ago

#15881 new Cleanup/optimization

FilteredSelectMultiple does not respect order

Reported by: bmihelac Owned by: nobody
Component: contrib.admin Version: 1.3
Severity: Normal Keywords: javascript
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: yes

Description

WIth filter_horizontal and filter_vertical admin options moving items between available and chosen, these items would be moved to the end of respecting lists.

I believe it would be useful and not so hard to preserve original sort order.

Change History (3)

comment:1 by Carl Meyer, 14 years ago

Triage Stage: UnreviewedAccepted

comment:2 by Julien Phalip, 14 years ago

UI/UX: set

comment:3 by Timothée Mazzucotelli, 9 years ago

It also does not respect the order of the selected choices when the form is reloaded.

I am using a comma-separated-value-list custom field, which stores choices id in the form of a string (ex: '12,66,13').
When retrieved from the database, this field is converted into a list using the split method. The example above would be converted to [12, 66, 13].

The first time you create the object with a new form, you can select choices one by one, and their order is kept in the resulting list (because they are added one by one in the widget right/bottom part by the javascript function). But when you save it and reload the page, the order is no longer maintained since it is lost while rendering the HTML. The selected part is now ordered the same way the available choices are. Indeed the javascript code just iterate over the "options" of the available part and add them in the chosen part if it encounters the "selected" attribute.

The simplest solution I could think of to solve this is to add another attribute or class to the HTML "option" elements with the index of the selected choice as value, and use this new value in the javascript code to move them in the chosen part according to it.

Workaround

Python code in django.contrib.admin.widgets.Select.render_option

if option_value in selected_choices:
    selected_html = mark_safe(' selected="selected" order="%s"' % \
        selected_choices.index(option_value))

This would also require to have a list instead of a set (in render_options), because set are unordered:

# selected_choices = set(force_text(v) for v in selected_choices)
selected_choices = list(force_text(v) for v in selected_choices)
# Is set used to remove duplicates? If yes then
# we need more code to remove them from the list too

Generated HTML

<select multiple="multiple" class="filtered" id="id_example_from" name="example_old">
  <option title="Choice 1" value="75">Choice 1</option>
  <option title="Choice 2" value="73">Choice 2</option>
  <option title="Choice 3" value="66" selected="selected" order="1">Choice 3</option>
  <option title="Choice 4" value="13" selected="selected" order="2">Choice 4</option>
  <option title="Choice 5" value="54">Choice 5</option>
  <option title="Choice 6" value="12" selected="selected" order="0">Choice 6</option>
</select>

JavaScript code in SelectBox.js

move: function(from, to) {
    var from_box = document.getElementById(from);
    var to_box = document.getElementById(to);
    var option;
    var ordered_options = [];
    for (var i = 0; (option = from_box.options[i]); i++) {
        if (option.selected && SelectBox.cache_contains(from, option.value)) {
            ordered_options.push(option);
        }
    }

    ordered_options.sort(function(a, b) { return a.order - b.order; })

    for (var i = 0; i < ordered_options.length; i++) {
        SelectBox.add_to_cache(to, {
            value: ordered_options[i].value,
            text: ordered_ options[i].text, displayed: 1
        });
        SelectBox.delete_from_cache(from, ordered_options[i].value);
    }
    SelectBox.redisplay(from);
    SelectBox.redisplay(to);
},

This is just a proposition, I didn't test it yet, but I'll post some results when I do.

Note: See TracTickets for help on using tickets.
Back to Top