One of the basic needs of a user is to filter information. From a table-of-contents of hundreds of articles I want to see just those that contain information about, let's say, JavaScript. I assume the article's title contains enough information to decide whether it's about JavaScript, so that filtering can happen on the client-side browser without contacting the server.
Mostly filtering is provided by one or more text-fields where the user can input a search-pattern. But using the keyboard is getting tedious nowadays when being on a mobile phone. Patterns might be even misunderstood (wildcards), moreover most words are not precise without context. It could also happen that the user inputs "JavaScript", but the title of the searched article contains the synonym "JS".
To make filtering easy and quick I came up with a set of checkboxes that describe most of the articles' contents. That means the search-patterns have been pre-defined, and the user chooses from a restricted set of filterings. This moves the responsibility to define search-terms to the page-author.
Example
The following list contains links to articles about JavaScript, CSS, HTML, Java and LINUX. If a title contains "JS", "JavaScript" or "jQuery", it is expected to be about JavaScript, "HTML" and "Page" lead to HTML, "CSS" to CSS, and so on. Should a title contain both "JavaScript" and "CSS", it would be in both search results. Should we want to see none of these categories, we can click the "Others" checkbox, this excludes all the mentioned search terms, and displays just those titles that do not refer to any of them.
Blog Archive Contents
This table-of-contents has been generated by my BlogSaver Java application, which I wrote about in a passed Blog.
HTML
Here is how checkbox-filtering works.
<p> <label><input type='checkbox' search-words='JS, JavaScript, jQuery' onclick='filter();'/> JavaScript </label> <label><input type='checkbox' search-words='CSS, Cascading Style Sheets' onclick='filter();'/> CSS </label> <label><input type='checkbox' search-words='HTML, Page' onclick='filter();'/> HTML </label> <label><input type='checkbox' search-words='Java, Constructors' onclick='filter();'/> Java </label> <label><input type='checkbox' search-words='LINUX, UNIX, vi, AWK, GIMP' onclick='filter();'/> LINUX </label> <label><input type='checkbox' id='Others' onclick='filter();'/> Others </label> </p> <table> <tr> <td>104</td> <td><a href='The_JS_Function_in_Loop_Bug.html'>The JS Function-in-Loop Bug</a></td> <td>2016-06-07</td> </tr> ........ <tr> <td>1</td> <td><a href='Things_Are_Changing.html'>Things Are Changing</a></td> <td>2008-02-26</td> </tr> </table>
This is a set of checkboxes that all call the JS function filter()
on click.
Each of these has an attribute search-words
that contains the terms that must occur in the according article title.
This is what the user normally would have to input, but with these attributes I can give all synonyms in one place.
There are two types of search-words, one contains no space, the other is a phrase and thus contains space: "Cascading Style Sheets" goes with "CSS". Thus the terms are comma-separated.
Another difficulty is that the occurrence of "JavaScript" in a title must not lead to finding this as "Java" article. We need a search with word-boundaries.
The table
element is (an outline of) the list to filter.
JavaScript
Here is the (preliminary) source code of the filter()
function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | <script type='text/javascript'> var toArrayWithoutEmpty = function(csv, splitChar) { var rawSearchPatterns = csv.split(splitChar); var searchPatterns = []; for (var i = 0; i < rawSearchPatterns.length; i++) { var searchPattern = rawSearchPatterns[i].trim(); if (searchPattern !== '') { var lastChar = searchPattern.charAt(searchPattern.length - 1); if (lastChar === '!' || lastChar === '?' || lastChar === '.' || lastChar === ',') searchPattern = searchPattern.substring(0, searchPattern.length - 1); searchPatterns.push(searchPattern); } } return searchPatterns; }; var matches = function(searchPatterns, labelText, labelWords) { for (var i = 0; i < searchPatterns.length; i++) { var searchPattern = searchPatterns[i]; if (searchPattern.indexOf(' ') > 0) { if (labelText.indexOf(searchPattern) >= 0) return true; } else if (labelWords.indexOf(searchPattern) >= 0) { return true; } } return false; }; var filter = function() { var table = document.getElementsByTagName('table')[0]; var checkboxes = document.getElementsByTagName('input'); var allSearchWords = ''; var searchWords = ''; var othersIsChecked = false; for (var i = 0; i < checkboxes.length; i++) { var checkbox = checkboxes[i]; var thisSearchWords = checkbox.getAttribute('searchWords'); if (thisSearchWords) { allSearchWords += ', '+thisSearchWords; if (checkbox.checked) searchWords += ', '+thisSearchWords; } else if (checkbox.checked && checkbox.getAttribute('id') === 'Others') othersIsChecked = true; } var searchPatterns = toArrayWithoutEmpty(searchWords, ','); var allSearchPatterns = toArrayWithoutEmpty(allSearchWords, ','); var tbody = table.children[0]; for (var row = 0; row < tbody.children.length; row++) { var tableRow = tbody.children[row]; var label = tableRow.children[1].children[0].childNodes[0]; var labelText = label.textContent.trim(); var labelWords = toArrayWithoutEmpty(labelText, ' '); var match = false; if (searchPatterns.length > 0 || othersIsChecked && allSearchPatterns.length > 0) { if (othersIsChecked) match = ! matches(allSearchPatterns, labelText, labelWords); match = match || matches(searchPatterns, labelText, labelWords); } else match = true; tableRow.style.display = match ? '' : 'none'; } }; </script> |
Yes, you are right, this is hacker code! It contains significant code-smells, and I better should not show this here, because it could ruin my reputation as a software developer :-!
Problems are:
- big function
filter()
- lacking reusability (DOM access has not been encapsulated in functions, source specializes on checkboxes, ...)
- missing encapsulation (all functions are globally visible)
- no parametrization (does not allow to use another attribute-name than
search-words
) - bad naming (parameter
csv
, although not always dealing with comma-separated-values) - error-prone implementations (upper/lower case characters are not covered, inline String operations)
- separation of concerns violation (event-listener installation is done in HTML, not in JS)
- documentation is absent
Thus I won't explain this source here and now line by line, because it is not worth. Moreover I will describe how to refactor this in my next Blog, and then it should also become clear how it works.
So don't copy this source code, you will have problems making it work in your page. It's just here to demonstrate how freshly written source code looks like. Find the refactored code of this in follower-Blog(s), and thereby learn how to refactor JavaScript to a reusable module.
Keine Kommentare:
Kommentar veröffentlichen