0

I am having a select dropdown which is being used alot every day by a lot of different people.

Our idea was to personalize this select for the users.

So, for example, a user makes the following choices:

- Option a: 5 times
- Option c: 3 times
- Option f: 1 time

Then we would like the select to be ordered a, c, f, b, d, e. In short, we want the most popular items for that user to show on top.

Now, sure I know that I could make such a thing using jquery and a cookie but I was wondering if there is already a solution around that does this.

Edit: I have already googled this but didn't find anything that came close to what I'm searching for.

Peter
  • 8,776
  • 6
  • 62
  • 95
  • So you would like to store how many times a person selected certain option? How do you load those options currently? – carlosherrera Nov 11 '14 at 15:38
  • The options come from my mysql database. However a position row is not an option as that isn't a per user solution – Peter Nov 11 '14 at 15:41
  • where does the data for your select come from? – baao Nov 11 '14 at 15:42
  • As mysql isn't a option, local storage might be your first choice. See here a good example: http://stackoverflow.com/questions/2010892/storing-objects-in-html5-localstorage – baao Nov 11 '14 at 15:52
  • since it's user specific can store usage in localStorage and sort the html based on stored values – charlietfl Nov 11 '14 at 16:04
  • (1) *I know that I could make such a thing using jquery and a cookie*: Yes. That is your best bet. If you want to know how, improve your question clarifying where are you stuck. (2) *I was wondering if there is already a solution around*: This statement may get this question closed soon on account of "asking us to recommend or find a book, tool, software library, tutorial or other off-site resource are off-topic for Stack Overflow". – Abhitalks Jan 02 '15 at 08:34

4 Answers4

2

If you want to personalize your site to users, I'd recommend avoiding client side only solutions and rely on server side. You never know when your user's localStorage, for instance, would be deleted. Also you might want to personalize additional data in the future and this could get not very scalable.

My solution would be to keep an extra table on the database, with a foreign key to your user id (I assume you keep them listed), that keeps his preferences. You should update this table after every selection that the user makes (for example, posting the form to your site -> update that table).

Now, whenever you're loading that page you want personalized, download to the client your select options ordered according to the user's preferences, i.e., according to the new table in the database.

Omri Aharon
  • 16,959
  • 5
  • 40
  • 58
  • 1
    Agreed. This is providing a solution in the right direction, rather than just an "answer". – Leo Jan 02 '15 at 13:29
1

To personalize the select so that it shows a list of options sorted in the order of "Most Frequently Used" ones, we can have best of both the world's in your wishlist. Let's create one using jQuery and cookies. And also, make a re-usable solution which is ready to use. With the added advantage of you being able to modify it according to your liking.

Before we do that, just a note of caution. If you are looking to personalize a lot of things, then it is better to do that server-side and create a personalization subsystem in your app. Relying on client-side cookies is not advisable for such scenarios. However, if your use-case is just a one-off scenario like this one, where you simply want to enhance user experience by providing an extra enhancement over and above the working feature-set without making changes to the server-side code, then it makes perfect sense to implement such functionality client-side. That is exactly why cookies and localstorage are there. Just like advertisers use tracking cookies, this is also like a tracking cookie which is tracking what your users are using more frequently. Isn't it?

That said, let's create a jQuery plugin which you can consume as-is. It will also allow you to apply it to more than one select. Let's call it .mfu().

Note: The code here is just a crude demo for you. It is not optimized and perhaps needs re-factoring. Currently, this plugin will fail, if you do not give a unique id to all your selects, as it depends on id to attach and retreive the frequency.

You can view the complete demo here: http://jsfiddle.net/abhitalks/u5z868d0/

Select an option from both the selects and keep clicking "run" in the fiddle. You will see that the most frequently used options keep getting to top.


We'll start with a blank plugin template which looks like this:

(function ($) {
    $.fn.extend({   
        mfu: function () { 
            ....
            return this.each(function () { 
                ...
            });
        }
    }); 
})(jQuery);

We go through each of the selects on which the plugin was called and return all of them back for chaining.

You can then call this plugin on all your selects like this:

$(select).mfu();

We will store our option details along with the frequencies in an object array for all of the selects specified in the selector on which the plugin is called. The object array looks like this:

[
    {
        'id': 'drop1', 
        'mfu': 
            [
                { 'value': 'a', 'caption': 'Red', 'frequency': 0 },
                { 'value': 'b', 'caption': 'Green', 'frequency': 0 },
                ...
            ]
    }, 
    {
        'id': 'drop2', 
        'mfu': 
            [
                { 'value': '1', 'caption': 'One', 'frequency': 0 },
                { 'value': '2', 'caption': 'Two', 'frequency': 0 },
                ...
            ]
    }
];

Now, let's start coding the plugin. First thing to do is some initialize stuff. We will store the "frequency" of each option in each select for our "Most Frequently Used" items in a cookie. So, our initialize code will include code to read the cookie on initialize and load the "Most Frequently Used" items in an object array. We will also need to write back the cookie, so let's create a function to do that here itself. Because, we will be working with an object array, we will use JSON to stringify() it to write to cookie, and parse() on read.

So, our plugin now starts looking like this:

(function ($) {
    $.fn.extend({   
        mfu: function () { 
            var self = this, cookieName = 'mfu', 
                defaultExpiry = new Date(2050, 1, 1), 
                getCookie = function(name) {
                    var results = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
                    return results ? unescape(results[2]) : null;
                },
                setCookie = function(name, value) {
                    var cookieString = name + "=" + escape(JSON.stringify(value));
                    cookieString += "; expires=" + defaultExpiry.toGMTString();
                    document.cookie = cookieString;
                }, 
                mfuStored = getCookie(cookieName), 
                mfuList = JSON.parse(mfuStored)
            ;

            return self.each(function () { 
                ...
            });
        }
    }); 
})(jQuery);

We have hard-coded the cookie name as "mfu". We have also set a far expiry date for the cookie. In real-life code, you must look into dynamically setting this. Then there are routine read/write functions for cookies. We read the cookie and load the MFU list into a variable mfuList which is an object array.

Next, we go through each of the selects on which the plugin was called and then search in our object array for the id of each select. If not found, that means it has been run for the first time (and/or cookie is not available), in this case we create new object to hold the frequencies. If found, we then find the options in that select and check if the frequencies are available on cookie-read-load. If not found, we create the new objects to hold the option details.

Once that is done, we sort our object array for the current select and then replace the select options in the sorted order. We can also show the frequencies to the user if required.

Now, all that remains, is to bind the change event on each select to update the frequency of the selected option in our object array and save that back to the cookie. In this demo, we are saving it on change event. In real life, you could save the cookie when the page unloads.

So, now our complete plugin looks like this:

(function ($) {
    $.fn.extend({   
        mfu: function () { 
            var self = this, cookieName = 'mfu', 
                defaultExpiry = new Date(2050, 1, 1), // far ahead expiry of cookie
                getCookie = function(name) {          // routine to read cookie
                    var results = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
                    return results ? unescape(results[2]) : null;
                },
                setCookie = function(name, value) {   // routine to write cookie
                    var cookieString = name + "=" + escape(JSON.stringify(value));
                    cookieString += "; expires=" + defaultExpiry.toGMTString();
                    document.cookie = cookieString;
                }, 
                mfuStored = getCookie(cookieName),    // read the cookie
                mfuList = JSON.parse(mfuStored)       // parse object array from the string
            ;

            return self.each(function () { 
                var select = this, mfu;
                if (! mfuList) {            // for the first time, create object
                    mfuList = []; mfuList.push({ 'id': select.id, 'mfu': [] });
                }

                // search for the current select
                mfu = mfuList.filter(function(o) {
                    return o.id === select.id;
                })[0];

                if (!mfu) { // if not found, create object and add to array
                    mfuList.push({ 'id': select.id, 'mfu': [] }); 
                    mfu = mfuList[mfuList.length - 1].mfu;
                } else {
                    mfu = mfu.mfu; // if found, use the mfu property
                }

                // for each option search the object arrat
                [].map.call(select.options, function(option) {
                    var opt = mfu.filter(function(o) {
                        return o.value === option.value;
                    })[0];
                    if (! opt) { // if not found, create object and add to array
                        opt = {} ;
                        opt.value = option.value;
                        opt.caption = option.innerText;
                        opt.frequency = 0;
                        mfu.push(opt);
                    }
                });

                // sort the array on frequency
                mfu.sort(function(a, b) { return a.frequency < b.frequency });

                // empty the select
                $(select).empty();

                // loop thru our object array and to select in sorted order
                mfu.forEach(function(item) {
                    var $opt = $("<option>");
                    $opt.val(item.value);
                    $opt.text(item.caption + ': (' + item.frequency + ')');
                    $(select).append($opt);
                });

                // add event listener for change
                $(select).on("change", function() {
                    var value = this.value;
                    // search our object array
                    var opt = mfu.filter(function(o) {
                        return o.value === value;
                    })[0];
                    opt.frequency++; // increment the frquency
                    setCookie(cookieName, mfuList); // write back the cookie
                });
            }); 
         } 
    }); 
})(jQuery);

// call the plugin on all selects
$("select").mfu();

That's all there is to it. You can now improvise and improve this code further if required, otherwise it is ready to be used as-is.

.

Abhitalks
  • 27,721
  • 5
  • 58
  • 81
0

i have made some small POC on this, is you have data how may times user has clicked on the option this POC may use full for you

HTML code :

<select id="selectbox">
    <option data-sort="9">option 1</option>
    <option data-sort="8">option 2</option>
    <option data-sort="1">option 3</option>
    <option data-sort="3">option 4</option>
    <option data-sort="4">option 5</option>
    <option data-sort="5">option 6</option>
    <option data-sort="6">option 7</option>
    <option data-sort="1">option 8</option>
    <option data-sort="1">option 9</option>
    <option data-sort="3">option 10</option>
    <option data-sort="4">option 11</option>
    <option data-sort="7">option 12</option>
    <option data-sort="8">option 13</option>
    <option data-sort="2">option 14</option>
    <option data-sort="1">option 15</option>
    <option data-sort="4">option 16</option>
</select>

JQuery Code :

function srt(on,descending) {
         on = on && on.constructor === Object ? on : {};
         return function(a,b){
           if (on.string || on.key) {
             a = on.key ? a[on.key] : a;
             a = on.string ? String(a).toLowerCase() : a;
             b = on.key ? b[on.key] : b;
             b = on.string ? String(b).toLowerCase() : b;
             // if key is not present, move to the end 
             if (on.key && (!b || !a)) {
              return !a && !b ? 1 : !a ? 1 : -1;
             }
           }
           return descending ? ~~(on.string ? b.localeCompare(a) : a < b)
                             : ~~(on.string ? a.localeCompare(b) : a > b);
          };
        }
        var boxarray=[];
        $("#selectbox").click(function(){
            boxarray=[];
            $("#selectbox option").each(function(){
                var cValue = $(this).val();
                var Cattr = $(this).attr("data-sort");
                var newObj = {"sort":Cattr,"value":cValue};
                boxarray.push(newObj);
            });
            boxarray.sort(srt({key:'sort',string:true},true));
            console.log(boxarray);
            var optionsString = "";
            for(i=0;i<boxarray.length;i++){
              optionsString+= '<option data-sort="'+boxarray[i].sort+'">'+boxarray[i].value+'</option>';
            }
            console.log(optionsString);
            document.getElementById("selectbox").innerHTML = "";
            document.getElementById("selectbox").innerHTML = optionsString; 
        });

Code pen URL : http://codepen.io/naveenkumarpg/pen/emBdgV/

Hope this may help you.

Naveen Setty
  • 615
  • 5
  • 26
0

As others have said, this would be better if done server side, especially since a user can disable javascript.

But, this example it assumes that the select is already populated, and it rebuilds the select list as desired (demo):

var indx,
    xref = {},
    newHtml = '',
    $select = $('select'),
    opts = $select.find('option')
    // build array of options & count repeats
    .map(function () {
        var v = this.value;
        if (xref[v]) {
            xref[v]++
        } else {
            xref[v] = 1;
        }
        return v;
    })
    // sort options by adding "0" or "1" in front 
    // to perform a weighted sort so duplicates get sorted first
    .sort(function(a, b){
        var x = xref[a] > 1 ? '0' + a : '1' + a,
            y = xref[b] > 1 ? '0' + b : '1' + b;
        return x === y ? 0 : x > y ? 1 : -1;
    }),
    // return unique array values
    arr = $.grep(opts, function (v, k) {
        return $.inArray(v, opts) === k;
    });

// replace current options with new list
for (indx = 0; indx < arr.length; indx++) {
    newHtml += '<option value="' + arr[indx] + '">' + arr[indx] + '</option>';
}
$select.html(newHtml);
Mottie
  • 84,355
  • 30
  • 126
  • 241