jEditable select dropdowns change the order of the options
Asked Answered
H

5

6

Here's the code I'm using to add a select dropdown to a table row using jEditable:

$('tr',this).find('td:eq(8)').css('cursor','pointer')
    .editable( 'update.php', {
        'type': "select",
        'loadurl': 'json.php',
        'onblur': 'submit',
        'callback': function( value, settings ) {
         // etc.
        }); 

The problem is that the options in the dropdown aren't sorted in the desired order. The original JSON coming from json.php is ordered by first name (as per client's request):

{"0":"Select driver...", "48":"Ashley Solis", "43":"Casey Segal", // etc. 

but in the final webpage, the options in the select dropdown are sorted by number.

It seems that jEditable is re-sorting the JSON data when I don't want it to. How do I fix this?

Hypoglycemia answered 20/8, 2013 at 16:26 Comment(2)
Just a note, not a solution, you might be interested in X-editable. It is very feature rich and flexible.List
@List Worth looking at, thanks. I picked jEditable in this case because it boasts integration with dataTables, which I'm also using.Hypoglycemia
H
4

The Cause

The root of the problem isn't in jEditable, it's in the fact that the JSON that it's using to create the select dropdown is a single object. JavaScript treats object properties as unsorted. This is the key code in jEditable 1.7.1 (starting at line 492):

 /* If it is string assume it is json. */
 if (String == data.constructor) {      
     eval ('var json = ' + data);
 } else {
 /* Otherwise assume it is a hash already. */
     var json = data;
 }
 for (var key in json) {
 // starts looping through the object properties

Normally, that for (var key in json) should loop through the JSON properties in the order that they're created. Instead, they're looped in numerical ("alphabetical") order.


The Fix

There are many ways to solve this, but the simplest is through an anonymously-authored jEditable plug-in I found on Pastebin through a Google search. Paste this into a new JavaScript file, and load it in your HTML file after jEditable:

<script src="/js/jquery.jeditable.min.js"></script>
<script src="/js/jquery.jeditable.plugins.js"></script>

In addition, the json.php file must be altered. Instead of returning JSON as an object, like this:

{"0":"Select driver...", "48":"Ashley Solis", "43":"Casey Segal", // etc. 

It should return JSON as a linear array that looks like this:

[" Select driver...||0","Ashley Solis||48","Casey Segal||43", // etc.

Note the use of whitespace at the start of the string "Select driver...", which ensures that it will be sorted to the first position. No other JavaScript needs to be changed, provided that json.php isn't being used anywhere else.


The Plugin

Here's the complete jEditable plugin I found on Pastebin:

// type: select (based on 1.7.1, added feature: sorted options)
$.editable.addInputType("select", {
    element: function (settings, original) {
        var select = $('<select />');
        $(this).append(select);
        return (select);
    },
    content: function (data, settings, original) {
        /* If it is string assume it is json. */
        if (String == data.constructor) {
            eval('var json = ' + data);
        } else {
            /* Otherwise assume it is a hash already. */
            var json = data;
        }

        var aData = [];
        for (var key in json) {
            if (!json.hasOwnProperty(key)) {
                continue;
            }
            if ('selected' == key) {
                continue;
            }

            aData.push(json[key] + "||" + key);
        }

        // Sort
        aData.sort();

        // Create
        for (var key in aData) {
            var aDataSplit = aData[key].split("||");

            var option = $('<option />').val(aDataSplit[1]).append(aDataSplit[0]);
            $('select', this).append(option);
        }

        /* Loop option again to set selected. IE needed this... */
        $('select', this).children().each(function () {
            if ($(this).val() == json['selected'] || $(this).text() == $.trim(original.revert)) {
                $(this).attr('selected', 'selected');
            }
        });
    }
});
Hypoglycemia answered 20/8, 2013 at 16:26 Comment(1)
Quote: Normally, that for (var key in json) should loop through the JSON properties in the order that they're created -- but the act of copying data to json re-sorts those properties into numerical order. This is wrong. The object ist not copied; data and json are references pointing to the same object.Laze
O
10

A much easier workaround: Make the value non-numeric. Add a "_" at the beginning or something, and then strip it off when using the value after selection:

{"_0":"Select driver...", "_48":"Ashley Solis", "_43":"Casey Segal", // etc. 

That should preserve the order (at least in the browsers that I have tested).

Orthoptic answered 24/10, 2013 at 1:31 Comment(2)
The most effective and easiest solution ever. Thanks!Auriga
This isn't ideal if you have to submit the IDs back to the server. The server would need to also be modified to strip leading underscores. This makes the code fragile.Permute
H
4

The Cause

The root of the problem isn't in jEditable, it's in the fact that the JSON that it's using to create the select dropdown is a single object. JavaScript treats object properties as unsorted. This is the key code in jEditable 1.7.1 (starting at line 492):

 /* If it is string assume it is json. */
 if (String == data.constructor) {      
     eval ('var json = ' + data);
 } else {
 /* Otherwise assume it is a hash already. */
     var json = data;
 }
 for (var key in json) {
 // starts looping through the object properties

Normally, that for (var key in json) should loop through the JSON properties in the order that they're created. Instead, they're looped in numerical ("alphabetical") order.


The Fix

There are many ways to solve this, but the simplest is through an anonymously-authored jEditable plug-in I found on Pastebin through a Google search. Paste this into a new JavaScript file, and load it in your HTML file after jEditable:

<script src="/js/jquery.jeditable.min.js"></script>
<script src="/js/jquery.jeditable.plugins.js"></script>

In addition, the json.php file must be altered. Instead of returning JSON as an object, like this:

{"0":"Select driver...", "48":"Ashley Solis", "43":"Casey Segal", // etc. 

It should return JSON as a linear array that looks like this:

[" Select driver...||0","Ashley Solis||48","Casey Segal||43", // etc.

Note the use of whitespace at the start of the string "Select driver...", which ensures that it will be sorted to the first position. No other JavaScript needs to be changed, provided that json.php isn't being used anywhere else.


The Plugin

Here's the complete jEditable plugin I found on Pastebin:

// type: select (based on 1.7.1, added feature: sorted options)
$.editable.addInputType("select", {
    element: function (settings, original) {
        var select = $('<select />');
        $(this).append(select);
        return (select);
    },
    content: function (data, settings, original) {
        /* If it is string assume it is json. */
        if (String == data.constructor) {
            eval('var json = ' + data);
        } else {
            /* Otherwise assume it is a hash already. */
            var json = data;
        }

        var aData = [];
        for (var key in json) {
            if (!json.hasOwnProperty(key)) {
                continue;
            }
            if ('selected' == key) {
                continue;
            }

            aData.push(json[key] + "||" + key);
        }

        // Sort
        aData.sort();

        // Create
        for (var key in aData) {
            var aDataSplit = aData[key].split("||");

            var option = $('<option />').val(aDataSplit[1]).append(aDataSplit[0]);
            $('select', this).append(option);
        }

        /* Loop option again to set selected. IE needed this... */
        $('select', this).children().each(function () {
            if ($(this).val() == json['selected'] || $(this).text() == $.trim(original.revert)) {
                $(this).attr('selected', 'selected');
            }
        });
    }
});
Hypoglycemia answered 20/8, 2013 at 16:26 Comment(1)
Quote: Normally, that for (var key in json) should loop through the JSON properties in the order that they're created -- but the act of copying data to json re-sorts those properties into numerical order. This is wrong. The object ist not copied; data and json are references pointing to the same object.Laze
T
3

I had the same issue, ended up creating a custom input type called "server_ordered_select". To use it you need to do a few things:

  1. Change your server response to return an array of objects instead. Each object has only one key-value pair of id and label. For e.g.

    "[{'197':'Sterling'}, {'199':'US Dollar'}, {'185':'Canadian Dollar'}, {'200':'Yen'}]"
    
  2. Add the following somewhere before the jEditable init code:

    $.editable.addInputType('server_ordered_select', {
        element : function(settings, original) {
            var select = $('<select />');
            $(this).append(select);
            return(select);
        },
        content : function(data, settings, original) {
            /* If it is string assume it is json. */
            if (String == data.constructor) {
                eval ('var json = ' + data);
            } else {
                /* Otherwise assume it is a hash already. */
                var json = data;
            }
            var isArray = Object.prototype.toString.call(json) === '[object Array]',
                selected = "";
            for (var key in json) {
                var k, v;
                if (isArray) {
                    k = Object.keys(json[key])[0];
                    v = json[key][k];
                    if ('selected' == k) {
                        selected = v;
                        continue;
                    }
                } else {
                    if (!json.hasOwnProperty(key)) {
                        continue;
                    }
                    if ('selected' == key) {
                        selected = json['selected'];
                        continue;
                    }
                    k = key;
                    v = json[key];
                }
                var option = $('<option />').val(k).append(v);
                $('select', this).append(option);
            }
            /* Loop option again to set selected. IE needed this... */
            $('select', this).children().each(function() {
                if ($(this).val() == selected ||
                    $(this).text() == $.trim(original.revert)) {
                    $(this).attr('selected', 'selected');
                }
            });
        }
    });
    
  3. Now change the editable's type to 'server_ordered_select' in your editable init options.

    $('tr',this).find('td:eq(8)').css('cursor','pointer')
    .editable( 'update.php', {
        'type': "server_ordered_select",
        'loadurl': 'json.php',
        'onblur': 'submit',
        'callback': function( value, settings ) {
            // etc.
        });
    

The input type should also be compatible with the regular json responses for 'select' type, but then it won't keep the order from server.

Tanked answered 19/11, 2013 at 2:29 Comment(1)
While I think the plugin solution is simpler to implement, this might be a better approach for those who can't control the JSON data.Hypoglycemia
S
1

Was about to implement one of the above solutions, when I noticed that if you can adjust the JSON that is generated, X-Editable now support the following:

Source data for list. If array - it should be in format:

[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]

---8<---

Since 1.4.1 key children supported to render OPTGROUP (for select input only).

[{text: "group1", children: [{value: 1, text: "text1"}, {value: 2, text: "text2"}]}, ...]

Source: https://vitalets.github.io/x-editable/docs.html

Sonyasoo answered 3/6, 2016 at 13:34 Comment(0)
Z
0

I found a way to sort the JSON data by its values instead of the keys. There is a setting with jeditable that you can set in declaration called 'sortselectoptions'. Set this property to 'enabled' and this should sort your select-dropwdown by its display values.

   $(".edit-select").editable('@Url.Action("OptionInline", "BudOption")', {
        type : "select",
        style: "inherit",
        sortselectoptions: "enable",
        loadurl : '@Url.Action("GetOptionDrpJSON", "BudOption")',
Zippy answered 21/12, 2021 at 16:31 Comment(2)
I don't see any "sortselectoptions" attribute in jeditable. Where do you see it? Can you list to the version?Permute
@DimitriS. I could not find the the version of Jeditable I am using, but it is the older version that uses Jquery. I know there is a new version that uses type script so I am not sure which version you are using.Zippy

© 2022 - 2024 — McMap. All rights reserved.