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

A complicated process; won’t be easy to learn all of this. Don’t even know if I can!

Finally got the opportunity to test in IE9 and it seems to be working fine. This was with a single file; still have to test it with multiple files, which I hope will be next week.

Thanks again, Paul; all your hard work has been greatly appreciated!

1 Like

Hi Paul.

Have noticed another issue.

If I make a few selections from the list and then close any of the result divs, the list scrolls to whichever result I have closed and focus goes to that item in the list, which is perfectly fine; I think I like it this way, however, after making a few selections from the list, if I then search for something else by typing in the search field, the list then has to be scrolled to the top to see the first item in the new search instead of being there automatically, i.e., the list appears scrolled down instead of showing the new items from the top/first one.

To see what I mean, from the list, select T10 and then T20. Now type a ‘3’ in the search field. Instead of the list showing from the first item, i.e. ‘T3’, it shows from some way down the list and the list then has to be scrolled up to see the first item.

Before you go ahead and implement a solution, can I ask first, can this be fixed without affecting any other behaviour, such as that described at the beginning of this post?

Thanks.

Yes, we can update it so that when we initialize it, we can give it an afterSearch function. I’ll take a look at that tomorrow.

1 Like

OK, thank you very much, Paul.

The keyup event in the $.fn.filterCheckBoxesByText() function is where we’ll make some changes so that we can scroll back to the top when the search field is changed.

Refactoring

Some initial refactoring needs to occur to make it easier for us to work with the code. Currently the keyup event contains all of the functions. Those functions need to be moved out above the keyup method.

    $.fn.filterCheckboxesByText = function filterCheckboxesByText(searchField, opts) {
        var container = this;
        var showMatch = (opts && opts.showMatch) || true;

        function containsText(haystack, needle) {
            ...
        }
        ...
        function showItem(ignore, checkbox) {
            $(checkbox).parent("p").show();
        }

        $(searchField).bind("change keyup", function searchChangeHandler(evt) {
            var search = $(evt.target).val();
            ...
        });
    };

Scoped variables

Because one of the functions relies on the search variable, we have to move the declaration of that variable outside of the keyup method too.

    $.fn.filterCheckboxesByText = function filterCheckboxesByText(searchField, opts) {
        ...
        var search;
        ...
        $(searchField).bind("change keyup", function searchChangeHandler(evt) {
            search = $(evt.target).val();
        });
    };

Optional parameters

The $.fn.filterCheckboxesByText() function is already set up to receive optional parameters. We just need the keyup function to make use of them.

        $(searchField).bind("change keyup", function searchChangeHandler(evt) {
            ...
            if (typeof opts.afterSearch === "function") {
                opts.afterSearch();
            }
        });

it is in searchResults that we set up $.fn.filterCheckboxesByText()

    $(categories).filterCheckboxesByText($(searchField));

We can just add an object that contains our afterSearch() function.

    $(categories).filterCheckboxesByText($(searchField), {
        afterSearch: function doAfterSearch() {
            var firstResult = $(categories).children().first();
            scrollManager.moveToStart(firstResult);
        }
    });

Fixing a bug

When I made a mistake and uses resultsArea instead of categories, I found that a part of the code that uses tagName wasn’t working properly.

        while (!hasScrollContainerAsParent($el) && $el.tagName !== "HTML") {

This use of tagName is faulty because it only works on elements, but on jQuery objects.
If we want to use tagName we would have to get the element itself with .get(0) which is a valid option, but in the interests of readability we can use the jQuery .is() method instead.

        while (!hasScrollContainerAsParent($el) && !$el.is("html")) {

We are now checking for two inverted conditions which might be improved, since !A && !B is the same as !(A || B). We can instead check to see if we haven’t found a suitable parent or the top-level HTML element.

        while (
            !(hasScrollContainerAsParent($el) || $el.is("html"))
        ) {

Further work

There is only one other issue, and that is when you fully erase the search text. When that happens the focus leaves the search box, and moves to select the first result.

Do you want that search issue to be fixed, so that removing all of the search text has the focus remaining in the search field?

2 Likes

Thanks for your work on this, Paul. Sorry for my late response.

I am not sure I understand your question. Could you possibly give me an example? If you mean the following, then I would like it to remain the way it is, please.

If I search for ‘T1’, then select ‘T10’, ‘T100’ and ‘T101’ from the list and then erase ‘T1’ from the search box, the list scrolls back to the top of the list. If this is what you mean, at my end, focus (mouse cursor) still remains in the search box.

I’ve got a feeling, however, that you are referring to something else.

Thanks.

Yes, that’s what I mean. Currently it makes it more difficult when you want to erase the text and type in something else, because when you erase the text the focus goes down to the results pane, resulting in you having to click in the search area once again to type in your next search.

The better process is where you backspace to remove all of the text (or select all and delete), and when the search field is empty the focus remains in that search field, so that you can type in your new search text.

I think that it’s a bug when the empty search field makes the focus go down to the results. The focus should only go down to the results area when tab or the enter key is pressed. Right?

1 Like

Thanks for your reply, Paul.

It’s working correctly at my end (old Firefox); as stated in my last post, focus stays in the search box after erasing all the text typed in it, both in the fiddle, as well as in my own page.

Won’t be able to check it in IE till Monday, but will let you know once I do.

Thanks.

EDIT: Oh, do you mean the problem occurs AFTER you have corrected the issue in post #104 by implementing the code in post #107? If so, then yes, I would like it to work in the way that focus remains in the search box, please. Without your usual updated fiddle and demo file, I am unable to test it after your implementing any additional code.

Major problem, Paul!

Managed to check the page in IE11 and, unfortunately, it isn’t working. Typing in the search box does not do anything and neither does clicking on any item in the list. Works fine in IE9, Chrome AND my ancient Firefox.

I have had a brief search for possible issues on the web and there are a few mentions of UTF 8 encoded files causing problems. Thought I’d ask you first before attempting anything. Due to access issues, the file wasn’t transported to work via an ideal method so maybe that could be the issue; will try again and send the whole file instead of having to copy and paste the code from email and saving the text file by adding .htm at the end.

Thanks.

Panic over! The problem indeed seems to have been the way I must’ve saved the file as I manage to send myself the whole file and changed the extension and it worked fine this time. Sorry for any inconvenience.

Will you be updating the fiddle and uploading the files with the amended code from post #107?

Thanks.

It’s now getting more difficult to test that all of the code still works properly, due to the different things that it’s supposed to do. This is where automated code tests help you to know that things still work as they are supposed to.

Because these changes have been happening without using tests, there is now some pain involved in making further changes, and lots of fear of breaking things. To get rid of that pain and remove the fear, we need to put some tests in to place.

I’ll be using Jasmine to do those tests, and will update with information about how we go about using that process.

When all of the important pieces have tests, I’ll then come back to doing the update, using the tests to help make sure that things continue to work well.

1 Like

OK, thank you, Paul.

In the meantime, to reassure you, all the code is applied to my actual, full webpage (not just the fiddle or a sample webpage) and everything seems to be working correctly at present (apart from the issue in post #104). If you’d like me to check if any function is working in the real environment, just let me know and I will test it at my end.

Thanks.

Setting up the files

Getting started, download the jasmine standalone zip file from their releases section, and create some folders in the search-results folder, called lib, src, and spec.

The lib folder is where we put all of the library code that we have no reason to test, which are the escaperegexp.js and jquery.focusable.js files.
The src folder is where we place the remainder of our script files.
The spec folder will contain the tests (specifications) that make sure that our code remains in a good working condition.

From the downloaded jasmine file, copy the contents of its lib folder over to ours, and also copy the SpecRunner.html file in to our search-results folder. That’s all that we’ll need.

The contents of the search-results folder will now look like this:

│   index.html
│   SpecRunner.html
│   style.css
│
├───lib
│   │   escaperegexp.js
│   │   jquery.focusable.js
│   │
│   └───jasmine-2.6.4
│           boot.js
│           console.js
│           jasmine-html.js
│           jasmine.css
│           jasmine.js
│           jasmine_favicon.png
│
├───spec
└───src
        checkboxlist.js
        filtercheckboxesbytext.js
        queue.js
        resultscontroller.js
        scroll.js
        scrollmanager.js
        searchresults.js

As we’ve moved some files around, we need to update the index.html file so that the correct paths are used to refer to the script files.

  <script src="https://code.jquery.com/jquery-3.1.1.js"></script>
  <script src="lib/escaperegexp.js"></script>
  <script src="lib/jquery.focusable.js"></script>
  <script src="src/queue.js"></script>
  <script src="src/scroll.js"></script>
  <script src="src/scrollmanager.js"></script>
  <script src="src/filtercheckboxesbytext.js"></script>
  <script src="src/checkboxlist.js"></script>
  <script src="src/resultscontroller.js"></script>
  <script src="src/searchresults.js"></script>

Adding scripts to the test runner

The test runner will contain src and spec files from a demo, that we will replace.

  <!-- include source files here... -->
  <script src="src/Player.js"></script>
  <script src="src/Song.js"></script>

  <!-- include spec files here... -->
  <script src="spec/SpecHelper.js"></script>
  <script src="spec/PlayerSpec.js"></script>

The scripts that we updated in index.html, can be copied over to TestRunner.html where we add to the lib section, replace the ones in the src section, and add a spec file in the spec section.

  <script src="lib/jasmine-2.6.4/jasmine.js"></script>
  <script src="lib/jasmine-2.6.4/jasmine-html.js"></script>
  <script src="lib/jasmine-2.6.4/boot.js"></script>
  <script src="https://code.jquery.com/jquery-3.1.1.js"></script>
  <script src="lib/escaperegexp.js"></script>
  <script src="lib/jquery.focusable.js"></script>

  <!-- include source files here... -->
  <script src="src/queue.js"></script>
  <script src="src/scroll.js"></script>
  <script src="src/scrollmanager.js"></script>
  <script src="src/filtercheckboxesbytext.js"></script>
  <script src="src/checkboxlist.js"></script>
  <script src="src/resultscontroller.js"></script>
  <script src="src/searchresults.js"></script>

  <!-- include spec files here... -->
  <script src="spec/queue.js"></script>

We can create the spec/queue.js file with following code based on the introduction, and we’re ready to start testing,

/*jslint browser */
/*global describe, it, expect */
describe("Queue", function () {
    "use strict";
    it("contains spec with an expectation", function () {
        expect(true).toBe(true);
    });
});

Run the TestRunner.html file and you should see 1 spec, 0 failures appear.

Adding Queue tests

Because of how we’re coded our JavaScript, testing should be as easy as ensuring that the returned objects correctly do their job. With queue, that is init, remove, add, count, get, getAll, and contains. Let’s start with count.

Testing count

Before each test we want to start with a fresh new queue, so that any effects from a previous test will not interfere. We can tell Jasmine to create a new queue before each test.

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

We can now replace the contains spec with an expectation test with a proper test.

    it("gives a count of items in the queue", function () {
        expect(queue.count()).toBe(0);
    });

We need to make sure that this test code is working, so comment out from src/queue.js the count return statement and save, and you will see that the test runner when you reload the page shows an error. Uncomment that line and save, and reloading the test shows that it passes.

That fail to pass verification is an important part of the process, for it helps to make sure that the test is actually testing the right thing.

Testing add

We can now create a separate test that checks that the count increases when we add an item.

    it("adds items to the queue", function () {
        queue.add("text");
        expect(queue.count()).toBe(1);
    });

Testing remove

We can now add an item to the queue, remove it, and check that the count is zero.
We don’t need to check inbetween adding and removing that the count is 1, because that is already tested in the add test.

    it("Remove items from the queue", function () {
        queue.add("text");
        queue.remove("text");
        expect(queue.count()).toBe(0);
    });

Testing contains

We should check if the queue doesn’t contain an item first, before adding an item, and then after it’s removed we should also check that the queue doesn’t have that item.

    it("Checks if the queue contains an item", function () {
        expect(queue.contains("text")).toBe(false);
        queue.add("text");
        expect(queue.contains("text")).toBe(true);
        queue.remove("text");
        expect(queue.contains("text")).toBe(false);
    });

Testing get

When getting an item, problems might occur if the code always gives the first or the last item, so the most reliable test is to add three items and get the middle one.

    it("Gets an item from the queue", function () {
        queue.add("item 1");
        queue.add("item 2");
        queue.add("item 3");
        expect(queue.get(1)).toBe("item 2");
    });

Test getAll

When we get all items it gives us an array. The toBe() method checks that the expected and actual values are strictly equal (similar to the triple equals ===), which cannot occur with arrays. We need to use toEqual (a double equal comparison ==) for this test instead.

    it("Gets all items from the queue", function () {
        queue.add("item 1");
        queue.add("item 2");
        queue.add("item 3");
        expect(queue.getAll()).toEqual(["item 3", "item 2", "item 1"]);
    });

Queue code coverage

The Chrome browser lets us check the code coverage. We want our tests to exercise all parts of the code. With the above tests, queue is fully covered. That doesn’t ensure that it’s fully working, but it’s a good minimal check to make. Ideally we want all of the src files to have full coverage.

Test init

There’s one last queue thing to test, and that is the init() function. It should allow us to have multiple queues that don’t interfere with each other.

Using the given/when/then idea of testing, we first set up a given environment. When we make some changes to it, then we can test for changes.

    it("Allows multiple queues to be used", function () {
        var queue1 = Queue.init();
        var queue2 = Queue.init();

        queue1.add("item 1");
        queue1.add("item 2");

        expect(queue1.count()).toBe(2);
        expect(queue2.count()).toBe(0);
        expect(queue1.getAll()).toEqual(["item 2", "item 1"]);
        expect(queue2.getAll()).toEqual([]);
    });

Queue testing complete

That’s enough for testing the queue code. More can be added to it when bugs are fixed, or additional features are added. With bugs for example, we want to test for that bug so that it shows an error, and then update the code to fix the bug. It becomes a process of testing to show an error, then updating the code to give green results that fix the error.

I’ll get on with adding tests for the other src files, and give progress reports as I go.

2 Likes

OK, thanks Paul. If I try to follow what you are doing at my end, I hope it doesn’t matter that any practice will be on a Linux PC.

Hi Paul.

Was just wondering if we were any closer to solving the issue from post #104.

Also, IE11 is giving me a hard time! It’s probably nothing to with the code, but SharePoint and restrictions at work. The html file works fine in IE11 if saved on the pc, however, if uploaded to SharePoint and a link to it clicked, the functions do not work; no filtering search and no result DIVs appearing. No problem with IE9.

Any idea at all why an html file would work perfectly fine if clicked directly but not if a link to it is clicked?

Thanks.

[quote=“TechnoKid, post:118, topic:259656, full:true”]
Hi Paul.

Was just wondering if we were any closer to solving the issue from post #104.

Also, IE11 is giving me a hard time! It’s probably nothing to with the code, but SharePoint and restrictions at work. The html file works fine in IE11 if saved on the pc, however, if uploaded to SharePoint and a link to it clicked, the functions do not work; no filtering search and no result DIVs appearing. No problem with IE9.

Any idea at all why an html file would work perfectly fine if clicked directly but not if a link to it is clicked?[/quote]

I recommend that you investigate the console of the browser that’s having trouble. There should be some clues shown there.

The tests I’ve been making for the code have been on hiatus for a bit, but I should be able to get back to them tomorrow, if only to show the work that’s been done on them. Nobody likes doing tests, especially after the fact, which is one of the reasons why the process of crating the test first is so important. Another reason for doing tests is that you can be assured that the turning a failing tests in to a passing test means that your code changes were beneficial. And there also another aspect of doing tests, in that it helps to keep you on task and avoid distractions. But the main helpful reason for these tests are that they help to remove the fear of making changes. Changing code means that you risk breaking the code, and with tests in place you can rest assured that everything you’ve tested remains working afterwards too.

1 Like

Thanks for your suggestion and update, Paul.

It’s difficult to test anything in IE11 at the moment due to not having access to the software myself but will see what I can find at the next opportunity.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.