Multiple select list results in order of last selected on top?

Hi.

When I make a multiple selection from the list (see fiddle below), I cannot get the results (divs) to show in order of most recent selection at the top. For example, if I multi-selected in the order Nuts, Fruits, Vegetables, Meats, the results (divs) should appear in the order Meats, Vegetables, Fruits, Nuts. At the moment they appear in the order that they are listed in the box.

I have been trying for over four months to search for an answer myself on the web, however, have gotten nowhere especially as I do not wish to use a plugin, widget, etc.

I have been learning from examples on the web and making my own changes, as my knowledge in Javascript/JQuery is very limited. It would be more than appreciated if anyone can provide any help.

Thank you.

1 Like

Looping through each of the options wonā€™t give you the order that they were selected/

You will need to keep a separate array that records the index of the nth item that you selected.

1 Like

Hi Paul_Wilkins.

Thanks for taking the time to respond.

Are you aware of a similar example on the web which I may follow or which search terms I need to use to find the most relevant info? I have not been able to find anything similar. I am extremely puzzled as to why there are hardly any examples of something as common as wanting to order a list of results in a multiple select list!

Thanks.

1 Like

What you are wanting is a queue, and there are many queue libraries out there.

Your needs of a queue though are quite simple, so Iā€™ll take this opportunity to take you through using a testing library called Jasmine to derive the code that you need, from just a few simple tests.

Get the stand-alone version of Jasmine from https://jasmine.github.io/pages/getting_started.html via the More Information button, and follow the installation instructions.

You will be creating three files:

  • queue-test.html which is the test runner
  • queue.js which will contain the code for your test
  • spec\queue.spec.js which contains the tests themself

Add the HTML code from the Installation instructions to queue-test.html. Donā€™t forget to change the 2.0.0 in the installation instructions to 2.5.2, or to whichever version that you downloaded.

You will also need to add the script that you are testing, and the spec that tests the script too, to end up with the following:

queue-test.html

<!DOCTYPE html>
<html>
<head>
    <title>Queue test</title>
    <link rel="shortcut icon" type="image/png" href="jasmine/lib/jasmine-2.5.2/jasmine_favicon.png">
    <link rel="stylesheet" type="text/css" href="jasmine/lib/jasmine-2.5.2/jasmine.css">
</head>
<body>
    <script type="text/javascript" src="jasmine/lib/jasmine-2.5.2/jasmine.js"></script>
    <script type="text/javascript" src="jasmine/lib/jasmine-2.5.2/jasmine-html.js"></script>
    <script type="text/javascript" src="jasmine/lib/jasmine-2.5.2/boot.js"></script>
    <script type="text/javascript" src="queue.js"></script>
    <script type="text/javascript" src="spec/queue.spec.js"></script>
</body>
</html>

When running queue-test.html you should see a Jasmine test page that says, ā€œNo specs foundā€.
Opening the developer console you might also see that the queue scripts cannot be found. Either create the files, or correct the path so that they can be found.

Let us now give it a test.

Keeping it really simple, the first test just makes sure that the queue exists, which helps us to make sure that the testing environment is properly set up.
We always start with a failing test, so that we know that the code we write is what causes the test to pass.

spec/queue.spec.js

describe("Queue", function () {
    it("exists", function () {
        expect(queue).toBeDefined();
    });
});

With the above spec, the test page says ReferenceError: queue is not defined so letā€™s create the queue.

queue.js

var queue = function () {

};

Normally we would take things even slower by not even providing a function, which forces the test code to drive the direction of the code, but itā€™s all but a given that we will need a function here.

How is the test page looking? 1 spec, 0 failures - thatā€™s what we want.

Whatā€™s the next test? We will be wanting to add items to our queue, and eventually for the items to remain unique, so that adding an already existing item just brings it to the top of the queue.

One part at a time - starting with adding.

spec/queue.spec.js

    it("adds an item", function () {
        queue.add("Fruits");
        expect(queue.count()).toBe(1);
        expect(queue.get(0)).toBe("Fruits");
    });

Iā€™ve just made up those add, count, and get methods based on how I think that it should work.

Running the test shows us TypeError: queue.add is not a function so how are we to create that add method?

We could add it to the queueā€™s prototype, but a more acceptable technique is to use an IIFE and return an object that contains the desired functions from within it.

queue.js

var queue = (function iife() {
    function add() {

    }
    return {
        add: add
    };
}());

We now get a different error of TypeError: queue.count is not a function so letā€™s add that.

queue.js

var queue = (function iife() {
    function add() {

    }
    function count() {

    }
    return {
        add: add,
        count: count
    };
}());

How is our test looking now? We see Expected undefined to be 1.

Letā€™s make it pass in the simplest possible way:

queue.js

    ...
    function count() {
        return 1;
    }
    ...

This is not just a bloody-minded policy doing the simplest thing. It helps us to get to a passing test, from where we can add more tests to help push the code in more appropriate directions. This highly useful technique is called triangulation

The test now shows: TypeError: queue.get is not a function so letā€™s add it in a similar way to the others:

queue.js

    ...
    function get() {

    }
    return {
        add: add,
        count: count,
        get: get
    };
    ...

The test now shows: Expected undefined to be ā€˜Fruitsā€™. so letā€™s return that string, just the string from the get function. A separate test can help us to improve on that.

queue.js

    ...
    function get() {
        return "Fruits";
    }
    ...

And we have passing tests showing 2 specs, 0 failures

I wonā€™t show the ellipses in the code from now on to represent that itā€™s an excerpt.

Letā€™s now make the code more useful by adding a new test that adds two items:

spec/queue.spec.js

    it("adds two items", function () {
        queue.add("Fruits");
        queue.add("Vegetables");
        expect(queue.count()).toBe(2);
        expect(queue.get(1)).toBe("Vegetables");
    });

The test tells us, Expected 1 to be 2. so itā€™s time to add the items to an actual array.

queue.js

    var list = [];
    function add(item) {
        list.push(item);
    }
    function count() {
        return list.length;
    }

That looks like it should work, but the test tells us Expected 3 to be 2.

Why does it have 3 instead of 2? Ahh, because the queue still contains content from the previous test.
We need to create a new empty queue at the start of each test.

The standard notation here then is to use a capital initial letter for a constructor function, and to use something like Queue.init() to create a queue.

With our test we want a queue variable to be declared, so that before each of the tests we can create a new version of it.

spec/queue.spec.js

    var queue;
    beforeEach(function () {
        queue = Queue.init();
    });

The test now tells us ReferenceError: Queue is not defined so letā€™s get that sorted out.

Itā€™s easier that it seems, for we just need to remove the iife wrapper, renaming the function to init, and placing it in an object called Queue.

queue.js

var Queue = {
    init: function init() {
        var list = [];
        ...
        return {
            add: add,
            count: count,
            get: get
        };
    }
};

The test now tells us Expected ā€˜Fruitsā€™ to be ā€˜Vegetablesā€™. which should be easy to fix by updating the get function.

queue.js

        function get(i) {
            return list[i];
        }

And the test now tells us that everything is all good with 3 specs, 0 failures

The queue doesnā€™t yet work as we intend. When adding a new item to the queue, it should go to the top of the list, not to the bottom, so letā€™s update the test to show that.

spec/queue.spec.js

    it("adds two items", function () {
        queue.add("Fruits");
        queue.add("Vegetables");
        expect(queue.count()).toBe(2);
        expect(queue.get(0)).toBe("Vegetables");
        expect(queue.get(1)).toBe("Fruits");
    });

The test now tells us Expected ā€˜Fruitsā€™ to be ā€˜Vegetablesā€™. which is entirely expected. Instead of adding the new item to the bottom of the list, it needs to be added to the top.

So instead of using push, we can use the unshift method.

queue.js

        function add(item) {
            list.unshift(item);
        }

And the test shows everything passing with 3 specs, 0 failures

It would also help if we can get all of the items in the queue.

spec/queue.spec.js

    it("can get all items in the queue", function () {
        queue.add("Fruits");
        queue.add("Vegetables");
        expect(queue.getAll()).toEqual(["Vegetables", "Fruits"]);
    });

The test now shows TypeError: queue.getAll is not a function so letā€™s create it.

queue.js

        function getAll() {

        }
        return {
            add: add,
            count: count,
            get: get,
            getAll: getAll
        };

The test now shows Expected undefined to equal [ ā€˜Vegetablesā€™, ā€˜Fruitsā€™ ].

Which should be as easy as returning the list array.

queue.js

        function getAll() {
            return list;
        }

And the test is now all good, showing 4 specs, 0 failures

What else do we need from the queue? Moving an item to the top of the queue means removing it then adding it, so being able to remove an item from the queue is also going to be needed.

spec/queue.spec.js

    it("removes an item", function () {
        queue.add("Fruits");
        queue.add("Vegetables");
        queue.add("Nuts");
        queue.remove("Vegetables");
        expect(queue.getAll()).toEqual(["Nuts", "Fruits"]);
    });

The test tells us TypeError: queue.remove is not a function so letā€™s add that.

queue.js

        function remove(item) {
            
        }
        ...
        return {
            add: add,
            remove: remove,
            count: count,
            get: get,
            getAll: getAll
        };

We are now told by the test Expected [ ā€˜Nutsā€™, ā€˜Vegetablesā€™, ā€˜Fruitsā€™ ] to equal [ ā€˜Nutsā€™, ā€˜Fruitsā€™ ].

We can use the splice method to remove the item.

queue.js

        function remove(item) {
            list.splice(list.indexOf(item), 1);
        }

And the test now shows 5 specs, 0 failures

Will it remove something even if itā€™s not found?

spec/queue.spec.js

    it("only removes when an item is found", function () {
        queue.add("Fruits");
        queue.remove("Something else");
        expect(queue.getAll()).toEqual(["Fruits"]);
    });

The test shows [color=red]Expected [ ] to equal [ ā€˜Fruitsā€™ ].[color] Thatā€™s not good. We need to make sure that the item is found first before removing it.

queue.js

        function remove(item) {
            if (list.indexOf(item) > -1) {
                list.splice(list.indexOf(item), 1);
            }
        }

And the test once again shows 5 specs, 0 failures

We are now ready for the big one. When an already existing item is added, that should not change the queue size, and instead make its way to the top of the queue.

spec/queue.spec.js

    it("moves an existing item to the top", function () {
        queue.add("Fruits");
        queue.add("Vegetables");
        queue.add("Fruits");
        expect(queue.count()).toBe(2);
        expect(queue.getAll()).toEqual(["Fruits", "Vegetables"]);
    });

The test now tells us Expected 3 to be 2.

So, we need to check if the item is already in the list.

queue.js

        function add(item) {
            if (list.indexOf(item) === -1) {
                list.unshift(item);
            }
        }

Going back to the test, we are now told Expected [ ā€˜Vegetablesā€™, ā€˜Fruitsā€™ ] to equal [ ā€˜Fruitsā€™, ā€˜Vegetablesā€™ ].

So when the item is already in the list, we need to remove it and add it back to the top.

We already have a remove method, so this looks like itā€™s going to be quite simple:

queue.js

            if (list.indexOf(item) === -1) {
                list.unshift(item);
            } else {
                remove(item);
                add(item);
            }

The test now shows 6 specs, 0 failures, and I think that the queue code is all ready to be used.

Here it is in full, after the tests from the following post have been included.

queue.js

var Queue = {
    init: function init() {
        var list = [];
        function add(item) {
            if (list.indexOf(item) === -1) {
                list.unshift(item);
            } else {
                remove(item);
                add(item);
            }
        }
        function remove(item) {
            if (list.indexOf(item) > -1) {
                list.splice(list.indexOf(item), 1);
            }
        }
        function count() {
            return list.length;
        }
        function get(i) {
            return list[i];
        }
        function getAll() {
            return list.slice(0);
        }
        return {
            add: add,
            count: count,
            get: get,
            getAll: getAll,
            remove: remove
        };
    }
};

The tests that were used to come up with the above code are:

spec/queue.spec.js

describe("Queue", function () {
    var queue;
    beforeEach(function () {
        queue = Queue.init();
    });
    it("exists", function () {
        expect(queue).toBeDefined();
    });
    it("adds an item", function () {
        queue.add("Fruits");
        expect(queue.count()).toBe(1);
        expect(queue.get(0)).toBe("Fruits");
    });
    it("adds two item", function () {
        queue.add("Fruits");
        queue.add("Vegetables");
        expect(queue.count()).toBe(2);
        expect(queue.get(0)).toBe("Vegetables");
        expect(queue.get(1)).toBe("Fruits");
    });
    it("can get all items in the queue", function () {
        queue.add("Fruits");
        queue.add("Vegetables");
        expect(queue.getAll()).toEqual(["Vegetables", "Fruits"]);
    });
    it("protects the list from being changed", function () {
        queue.add("Fruits");
        queue.add("Vegetables");
        expect(queue.getAll()).toEqual(["Vegetables", "Fruits"]);
        queue.getAll().reverse();
        expect(queue.getAll()).toEqual(["Vegetables", "Fruits"]);
    });
    it("removes an item", function () {
        queue.add("Fruits");
        queue.add("Vegetables");
        queue.add("Nuts");
        queue.remove("Vegetables");
        expect(queue.getAll()).toEqual(["Nuts", "Fruits"]);
    });
    it("only removes when an item is found", function () {
        queue.add("Fruits");
        queue.remove("Something else");
        expect(queue.getAll()).toEqual(["Fruits"]);
    });
    it("moves an existing item to the top", function () {
        queue.add("Fruits");
        queue.add("Vegetables");
        queue.add("Fruits");
        expect(queue.count()).toBe(2);
        expect(queue.getAll()).toEqual(["Fruits", "Vegetables"]);
    });
});

Thanks to the tests, decisions being made were easily achieved, and the development of the code at each stage couldnā€™t be easier.

3 Likes

When making use of the queue with your code, a problem was found, where the getAll() method can be modified resulting in the queue being changed.

For example:

var reversed = queue.getAll().reverse();

Because the queue array is returned from getAll(), the array inside of the queue will end up being changed too.

Fortunately we can use the tests to help us fix this problem. Letā€™s use a test that demonstrates the problem, and tell us hat we want instead.

spec/queue.spec.js

    it("protects the list from being changed", function () {
        queue.add("Fruits");
        queue.add("Vegetables");
        expect(queue.getAll()).toEqual(["Vegetables", "Fruits"]);
        queue.getAll().reverse();
        expect(queue.getAll()).toEqual(["Vegetables", "Fruits"]);
    });

With that test we are told Expected [ ā€˜Fruitsā€™, ā€˜Vegetablesā€™ ] to equal [ ā€˜Vegetablesā€™, ā€˜Fruitsā€™ ].

The fix for this is to instead give a copy of the array, which can be done with:

queue.js

        function getAll() {
            return list.slice(0);
        }

And now the test tells us 7 specs, 0 failures

Another problem was found where when you attempt to remove and item that is not in the list, an item is still removed.

Hereā€™s the test that causes that strange behaviour to occur:

spec/queue.spec.js

    it("only removes when an item is found", function () {
        queue.add("Fruits");
        queue.remove("Something else");
        expect(queue.getAll()).toEqual(["Fruits"]);
    });

But instead, when running the test we are told Expected [ ] to equal [ ā€˜Fruitsā€™ ].

The solution to this is easy too, where we just check that the item exists before removing it.

queue.js

        function remove(item) {
            if (list.indexOf(item) > -1) {
                list.splice(list.indexOf(item), 1);
            }
        }

After which we are told 8 specs, 0 failures

Iā€™ll add these in to the final code shown at the end of the previous post.

2 Likes

So far as using this queue with your code, hereā€™s how itā€™s done.

First, the dropdown change event needs to go, because the value only shows the top-most value.
You are instead needing the one that was most recently clicked, which can be done by instead setting a click event on the options.

  $('#mydropdown option').click(function(evt) {
    var selectedOption = evt.target;
    var dropdown = this.parentNode;
    ...
  });

Because the click could be removing some items, we need to first hide all of the divs in the result area.

    ...
    var selectedOption = evt.target;
    var dropdown = this.parentNode;
    $('.mydivs1').each(function(index, div) {
      $(div).hide();
    });
    ...

Those divs also need to be placed inside of a parent div, so that we can most easily reorganise them on the screen.

<div id="results">
  <div class="mydivs1" id="idx Fruits" match="optionx Fruits">
    ...
  </div>
  <div class="mydivs1" id="idx Vegetables" match="optionx Vegetables">
    ...
  </div>
  ...
</div>

We are now ready to add the selected option to the queue, which occurs after hiding them.

    ...
    queue.add(selectedOption.value);
    ...

Once the selected option has been added, we need to go through all of the options, removing any that arenā€™t selected from the queue.

    ...
    Array.from(dropdown.options).forEach(function (option) {
      if (!option.selected) {
        queue.remove(option.value);
      }
    });
   ...

Finally, we want to get the items in the queue, reverse them, and move each one to the top of the results area. By going from the bottom up, we end up with them displayed from the top down.

    ...
    queue.getAll().reverse().forEach(function(item) {
      var info = $("[match='" + item + "']");
      $("#results").prepend(info);
      info.show();
    });
    ...

And thatā€™s all you need to get that part working. You can see it working at https://jsfiddle.net/pmw57/kbq7ymbk/2/

2 Likes

All I can say is ā€œWOWā€!!! Iā€™m speechless!

Canā€™t express just how much I appreciate your kindly taking the time to provide such a response!

I will look through your thorough response it as soon as I can, however, from just a quick look at the fiddle at the end, I am currently on an old version of Firefox whilst reading this so the problem may be at my end, but everything seems to be working fine except after I enter text to search, clicking the results do not make the relevant divs appear.

Is it working fine at your end?

Thanks.

Nothing has been said of the search up until now.
Your page is full of so many problems that itā€™s best to teach about them on a case by case basis.

1 Like

Currently the code to update the results is:

  $('#mydropdown option').click(function(evt) {
    var selectedOption = evt.target;
    var dropdown = this.parentNode;
    $('.mydivs1').each(function(index, div) {
      $(div).hide();
    });
    queue.add(selectedOption.value);
    Array.from(dropdown.options).forEach(function (option) {
      if (!option.selected) {
        queue.remove(option.value);
      }
    });
    queue.getAll().reverse().forEach(function(item) {
      var info = $("[match='" + item + "']");
			$("#results").prepend(info);
			info.show();
    });
  });

There is a problem, where the search area stops the result section from working.

One part of that problem can be dealt with by changing how the option event is handled, but another part of the problem will require a more extensive fix.

The first part of the problem is to do with the following line:

  $('#mydropdown option').click(function(evt) {

When the search area does its thing, it removes the options and replaces them with other ones. That causes the event that was assigned to the previous options to be destroyed.

Instead of assigning the click event to the options themself, we need to instead attach the event to the document, and tell it to only do the click event when suitable options are clicked.

You might want to read through the .on() docs as itā€™s an improvement jQuery made several years ago to how events are handled. The section most relevant to this situation here is on Direct and Delegated Events

Do not look at the sample code as that is not relevant, instead read the text

  $(document).on("click", "'#mydropdown option", function(evt) {

Another problem is that after youā€™ve made the search, clicking on an option doesnā€™t actually select it. Instead it seems to just focus the select area, after which a second click is required.

Fixing this can be tricky across multiple browsers, so the easiest solution is to just make the problem obvious to the user, where when leaving the search box we unselect all of the options.

In fact, no. After further exploration by removing any selected options, I find that the problem still occurs, triggered by other parts of the code. The real cause of the problem is that the search keyup event is being triggered when you leave the search, thus causing another unwanted and undesired update of the options.

    $(mytextbox).bind('change keyup', function(evt) {

The way to fix this problem is to check if any keyCode can be found. If not, then thereā€™s no reason to change the results. This here is also a good reason to add a comment, to help explain why something is happening.

    $(mytextbox).bind('change keyup', function(evt) {
    	if (!evt.keyCode) {
         return; // do not trigger when leaving the search field
      }

Now after youā€™ve done your search, you can select an option with only one click.

We now need to make further progress by moving the results section out to a separate function. While weā€™re doing this itā€™s a good opportunity to tidy things up as well.

What we want is to have only the following code in the event function:

  $("#mydropdown").on("click", "option", function(evt) {
    var selectedOption = evt.target;
  	updateResults(selectedOption, queue);
  });

Iā€™m also passing in the queue, because itā€™s a good practice for a function to only use that which has been given to it. Reaching out to access things from a global scope tends to be bad form, and causes problems such as spooky behaviour at a distance.

With the updateResults() function, Iā€™m going to move parts of the code out to separate functions. The benefit of doing this is that the updateResults function will end up being very easy to understand.

The first function to extract is one called hideResults(). It makes sense to pass in the selector that you want to hide here too.

  function hideResults(selector) {
    $(selector).each(function(index, div) {
      $(div).hide();
    });
  }

Now that weā€™ve isolated the code to this function, can it be improved? Can we hide multiple elements at the same time without needing the each method? You bet we can.

function hideResults(selector) {
$(selector).hide();
});
}


 That's now simple enough that it doesn't need to be in a separate function. Without having extracted it out though it's unlikely that we would have seen the opportunity for this improvement, so we can inline that function back in to the code.

$('.mydivs1').hide();

The next function to extract is an addOptionToQueue() function.

function addOptionToQueue(option, queue) {
queue.add(option.value);
}


The option and queue are easy parameters to give the function, and the function is doing a good job of handling the details of adding an item to the queue. Another reason for keeping this separate addOptionToQueue is that we also have a matching removeUnusedOptionsFromQueue() function.

function removeUnusedOptionsFromQueue(select, queue) {
Array.from(select.options).forEach(function (option) {
  if (!option.selected) {
    queue.remove(option.value);
  }
});

I'm in two heads about whether to keep the negative if condition, or to change it to a guard clause with an early return.

  if (option.selected) {
    return;
  }
  queue.remove(option.value);

Because the function is about removing unselected options, it makes better sense to keep the if condition being about unselected options too.

However, there is a school of thought that function names should not be about negative or inverted aspects. So instead of having removeUnselectedOptionsFromQueue() we should instead have it named filterQueueForSelectedOptions()

	function filterQueueForSelectedOptions(queue, select) {
    Array.from(select.options).forEach(function (option) {
      if (option.selected) {
        return;
      }
      queue.remove(option.value);
    });

Does that change how we do things in that function? You bet it does, for now that the focus is on retaining selected options, if we find a selected option then we want to make an early exit before the removal takes place.

The last function that we extract is the showResults() function, where it makes sense to pass in the targetSelector for where the results are to be shown.

  function showResults(results, targetSelector) {
    results.reverse().forEach(function(item) {
      var info = $("[match='" + item + "']");
      $(targetSelector).prepend(info);
      info.show();
    });
  }

Can we clean this up a bit? Possibly, by considering things from the perspective of info. Instead of prepending it to a target, we can use jQueryā€™s prependTo method instead, to end up with:

  function showResults(results, target) {
    results.reverse().forEach(function(item) {
      $("[match='" + item + "']")
        .show()
        .prependTo(target);
    });
  }

The benefit of all the above is that it now makes the updateResults() function so much easier to understand.

  function updateResults(option, queue) {
    var dropdown = option.parentNode;
    $('.mydivs1').hide();
    addOptionToQueue(option, queue);
    filterQueueForSelectedOptions(queue, dropdown);
    showResults(queue.getAll(), "#results");
  }

And the main reason for having this updateResults() method is that we can call it from the search function too, or when the search field is blurred, or when the select section gains focus.

  $(mytextbox).on('blur', function () {
    updateResults(undefined, queue);
  });

The only trouble to now resolve is what happens when no option is given. Problems are:

  • that you cannot add from a non-existing value
  • you cannot get the dropdown for the filter
  • filtering the queue currently relies on the available options

When the options are being filtered, removed options wonā€™t be checked against the queue resulting in those ones being left in.

The first problem can be resolved by checking if the option exists:

    if (option) {
      addOptionToQueue(option, queue);
    }

The second can be dealt with by passing the dropdown to the updateResults() function, and updating the filter so that it can handle either a direct reference to an element, or to a jQuery object.

    updateResults(selectedOption, selectedOption.parentNode, queue);
    ...
    updateResults(undefined, $("#mydropdown"), queue);

  function updateResults(option, dropdown, queue) {
    $('.mydivs1').hide();
    // var dropdown = option.parentNode; // removed
    ...
  }

  function filterQueueForSelectedOptions(queue, select) {
    Array.from($(select).options).forEach(function (option) {
    ...
  }

The last problem is dealt with by looping through each of the items in the queue, instead of relying on the dropdown options. That means updating the filter function to use the option values in the queue instead.

function filterQueueForSelectedOptions(queue, select) {
    queue.getAll().forEach(function (optionValue) {
      var option = $("option[value='" + optionValue + "']");
      if (option.prop("selected")) {
        return;
      }
      queue.remove(optionValue);
    });
  }

After that final part it all seems to work together well.

The updated code is:

    $(mytextbox).bind('change keyup', function(evt) {
      if (!evt.keyCode) {
        return;
      }
    ...
  function addOptionToQueue(option, queue) {
		queue.add(option.value);
  }
	function filterQueueForSelectedOptions(queue, select) {
    queue.getAll().forEach(function (optionValue) {
      if ($("option[value='" + optionValue + "']").prop("selected")) {
        return;
      }
      queue.remove(optionValue);
    });
  }
  function showResults(results, targetSelector) {
    results.reverse().forEach(function(item) {
      $("[match='" + item + "']")
        .show()
        .prependTo(targetSelector);
    });
  }
  function updateResults(option, dropdown, queue) {
    $('.mydivs1').hide();
    if (option) {
      addOptionToQueue(option, queue);
    }
    filterQueueForSelectedOptions(queue, dropdown);
    showResults(queue.getAll(), "#results");
  }
  $("#mydropdown").on("click", "option", function(evt) {
    var selectedOption = evt.target;
  	updateResults(selectedOption, selectedOption.parentNode, queue);
  });

  $("#mytextbox").on('blur', function () {
    updateResults(undefined, $("#mydropdown"), queue);
  });

You can see it in action at https://jsfiddle.net/pmw57/kbq7ymbk/4/

3 Likes

WOW again, Paul_Wilkins!

It seems to be working correctly; thank you!

Is there a reason I cannot have breaks between the divs?

There are two other features that I am struggling with; not sure if I need to create separate threads for those, so Iā€™ll mention them here since the code to which they apply is in this thread.

I wanted to be able to filter as many characters as possible and used the following:

var search = $.trim($(this).val().replace(/[-/{}()*+?.\^$|]/g, ā€œ\$&ā€));

Unfortunately, I want to be able to filter spaces also and thought adding /s to the above line would work, however, it hasnā€™t:

var search = $.trim($(this).val().replace(/[-/{}()*+?.\^$|\s]/g, ā€œ\$&ā€));

My example list doesnā€™t have any words with spaces, however, if, for example, ā€˜vege tableā€™ was in the list, typing a space should leave only ā€˜vege tableā€™ in the list.

I also wanted any letter typed into the search box to appear in bold (every instance) in the list, so, for example, typing an ā€˜eā€™ would make all three 'eā€™s in ā€˜vegetablesā€™ appear in bold, not just the first ā€˜eā€™. I have found the following fiddle, however, the only way I had got it to even begin to work was replacing my entire javascript with this code, whereas I want to make as few changes as possible to the checked and corrected code which you have so kindly written. I also do not wish it to be case sensitive as it is is the fiddle.

Thanks.

It is possible, but much more difficult to manage moving multiple elements around.
A much better solution is to use CSS to increase the bottom-margin of the div instead.

.mydivs1 {
  display: none;
  margin-bottom: 1em;
}

First of all before going in to the details - is it necessary to give people the ability to search by using regular expressions? Normally the answer to this is no - so why not just use the entered text to do the search?

      // var search = $.trim($(this).val().replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"));
      // var regex = new RegExp(search, "gi");
      var search = $.trim($(this).val());
      ...
      // if (option.text.match(regex) !== null) {
      if (option.text.indexOf(search) > -1) {

That way you can also easily find other occurrences of the search within the word later on too.
Follow the KISS principle (keep it simple, stupid) wherever possible.

HTML also doesnā€™t let you apply bold (or any HTML formatting) to selectbox options, so the need to do that may not even have to consider bolding text at all too.

I recommend that you get in touch with CSS people to find out the simplest way to style select box options, for they will be in the best place to provide recommendations on the best ways to achieve it, or other options that youā€™ll need to consider.

2 Likes

Thanks again, Paul_Wilkins.

Thanks for your advice on increasing the margin on the div instead; it works perfectly.

As stated in my original post, my knowledge is very limited and I have been using existing examples on the web and manipulating them to my requirements. I am all for keeping it as simple as possible so that I can understand it more easily and be able to troubleshoot if problems arise, however, the RegExp was already present in the code and as it did almost everything I wanted it to, I didnā€™t seek an alternative. I was just glad it worked!

I have replaced the RegExp/regex lines with the ones you have provided, however, in my browser, typing the first letter of an item in the list removes them all from the list, so typing a ā€˜vā€™, for example, leaves the list box empty. Letters in the middle work fine, however, so typing a ā€˜gā€™ leaves ā€˜vegetablesā€™ in the list but not if I type a capital ā€˜Gā€™. Also, searching for a space isnā€™t working with the new code.

Thanks also for your recommendation regarding considering CSS to address the emboldening issue. As the fiddle above didnā€™t contain any CSS, I had thought it was a javascript/JQuery matter.

Thank you again.

[quote=ā€œTechnoKid, post:12, topic:259656, full:trueā€]
I have replaced the RegExp/regex lines with the ones you have provided, however, in my browser, typing the first letter of an item in the list removes them all from the list[/quote]

Ahh good, we can just lowercase the text and the search, when making the comparison to resolve that issue.

if (option.text.toLowerCase().indexOf(search.toLowerCase()) > -1) {

As itā€™s the only place in the code that they need to be the same case, we donā€™t need to bother with assigning that lowercase version to any variables.

2 Likes

Thanks Paul-Wilkins; your new code has resolved the issue with the first characters AND case of characters.

Iā€™m still unable to filter a space. The fiddle I linked to in post #10 seems to filter a space correctly, however, I have no idea which part of the code does this and where it would need to go in any case!

Thanks again.

[quote=ā€œTechnoKid, post:14, topic:259656, full:trueā€]
Iā€™m still unable to filter a space.[/quote]

There is this part of the code that trims out any leading and trailing spaces.

var search = $.trim($(this).val());

I take it that you donā€™t want it to be trimmed now?

2 Likes

Thanks for the clarification, Paul_Wilkins.

There wonā€™t be any leading or trailing spaces in any of the items in my list/results, so does this mean that that part of the code is not required?

If that part of the code only trims leading and trailing spaces and does not affect how any other aspect works in any way, then, yes, I probably wouldnā€™t require it if it is preventing spaces from being filtered.

I have tried to remove it from the code, but it is still not filtering spaces.

That should be easy enough to test.

Remove the trim statement.

      var search = $(this).val();

And give it something with a space to search for:

<option value="optionx Vegetables">Vege tables</option>

It works for me :smiley:

2 Likes

ABSOLUTELY PERFECT!

Thank you so much for all your help, Paul-Wilkins! It has been more than appreciated!

I had removed the entire line!

As per your advice, will post in the CSS category for help with the emboldening.

Thanks again!

1 Like

Hi Paul_Wilkins; me again!

Sorry for being a pest but the search isnā€™t filtering for asterisks, question marks or opening/closing brackets?

Is it possible for it to? If asterisks or question marks are not possible or quite complicated, how about opening/closing brackets?

Thanks.

Hi Paul_Wilkins.

Sorry, ignore my previous post; it worked when I deleted the following line.

  var regex = new RegExp(search, "gi");

Iā€™m hoping you didnā€™t leave it in deliberately and I have now gone and broken something else in removing it!

Thanks.