Blog-Archiv

Sonntag, 12. Juni 2016

JS List Filter Checkboxes

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

104 The JS Function-in-Loop Bug 2016-06-07
103 JS jQuery $(this) 2016-06-06
102 LINUX Terminal ls colors 2016-06-06
101 HTML Elements and Dimensions 2016-06-04
100 JS Browser Reflow and Repaint 2016-05-29
99 JS Semicolons 2016-05-21
98 CSS Width 100% and Position Absolute or Fixed 2016-05-15
97 JS Map Key Gotcha 2016-04-30
96 JS Revealing Module Pattern 2016-04-24
95 What's HTML label for? 2016-04-18
94 Three Steps Toward Progress 2016-04-13
93 JS Get / Set Element Width / Height 2016-04-11
92 JS / CSS Tabcordion 2016-04-03
91 JS Responsive Breakpoint Notifications 2016-03-28
90 JS / CSS Tabs 2016-03-13
89 JS Table Layout Adjustment: DIV Tables 2016-03-06
88 JS Table Layout Adjustment: Predefined Widths 2016-02-29
87 JS Element Dimensions 2016-02-21
86 JS Table Layout Adjustment: Elastic Column 2016-02-14
85 JS Table Layout Adjustment: Sizing 2016-02-07
84 JS Table Layout Adjustment: Naming 2016-02-06
83 JS clientWidth and CSS width 2016-01-25
82 Space in HTML Breaks Layout 2016-01-19
81 JS Titled Border 2016-01-18
80 HTML Input Focus Tab Order 2016-01-16
79 JS Keyboard Events 2016-01-13
78 JS Sticky Bar 2016-01-07
77 Pure CSS Push Menu 2015-12-30
76 Replacement for CSS fixed position 2015-12-29
75 Pure CSS Slide Menu 2015-12-27
74 Protruding CSS inline elements 2015-12-26
73 Receiving CSS Events 2015-12-19
72 CSS BorderLayout, yet another one 2015-12-14
71 CSS height 100% shows Browser Scrollbar 2015-12-13
70 JS Browser Coordinates 2015-12-07
69 JS Sticky Table-of-Contents 2015-12-03
68 CSS Layout Test Page 2015-11-30
67 Text Outline with GIMP 2015-11-14
66 Iterator in Java and JS 2015-10-14
65 JS Natural Sort Order 2015-10-13
64 Natural Sort in Java, Performance Tuned 2015-10-11
63 Natural Sort in Java, First Performance Tuning 2015-10-07
62 Natural Sort Order in Java 2015-10-05
61 JS Light Bulb Moments 2015-10-03
60 JS Poor Developer's IDE 2015-09-19
59 Visitor Pattern for Test Data in Java 2015-09-17
58 Three Notorious Software Developer Habits 2015-09-09
57 Videotized on LINUX 2015-08-16
56 JS Visibility Detection 2015-08-14
55 LINUX Root Password Confusion 2015-08-01
54 CSS Fixed Table Header 2015-07-20
53 CSS Position Property 2015-07-19
52 JS Animated Expand and Collapse 2015-07-12
51 Pure CSS Menu 2015-07-11
50 JS Asynchronous Waiting 2015-06-20
49 JS Swipe Gesture 2015-06-11
48 The Immortal AWK Language 2015-05-27
47 JS Document Treeification 2015-05-15
46 Adjust Screen Coordinates in LINUX with Xfce 2015-05-09
45 UNIX Shell Control Structures 2015-05-01
44 vi Manual 2015-04-29
43 JS Table of Contents 2015-04-16
42 JS Overrides 2015-04-07
41 Extract Google Blog Export using Java 2015-04-04
40 JS Treetable 2015-03-31
39 Remote Desktop from LINUX to WINDOWS 2015-03-25
38 JS Slide Show Aftermath 2015-03-24
37 JS Slide Show 2015-03-14
36 Yet Another JavaScript AMD Loader 2015-02-28
35 JS Requires Dependency Management 2015-02-21
34 Interrupted LINUX Upgrade to Ubuntu 14.04 2015-02-13
33 JS Folding 2015-02-10
32 Many LINUX on board 2015-01-24
31 Installing LINUX on a DELL laptop besides WINDOWS 8.1 2015-01-12
30 Installing LINUX without CD or USB Stick 2015-01-05
29 A JS Framework for Rich Text Tooltips 2014-12-29
28 Preserve Inputs across Page Reload via JS 2014-12-25
27 The Self-Displaying Page 2014-12-20
26 jQuery for Beginners 2014-12-07
25 Object Relational Mapping with Inheritance in Java 2014-11-30
24 The State Pattern in Multiple Selection 2014-11-26
23 Good Documentation 2014-11-17
22 Sass Over CSS 2014-11-15
21 The Shape of Content as CSS 2014-11-06
20 How to Read Cascading Style Sheets 2014-11-04
19 A JS Starter Kit for Beginners 2014-10-29
18 JS Modules 2014-10-24
17 JS Functional Inheritance 2014-10-19
16 JS Inheritance contra Encapsulation 2014-10-16
15 Running JS Scripts from Java with HtmlUnit 2014-10-01
14 JS got cha 2014-09-30
13 This JS new ROFLCOPTER 2014-09-27
12 JavaScript Best Practices 2014-09-21
11 JS and the Forgotten Types 2014-09-20
10 Omigosh, JavaScript! 2014-09-19
9 Unbeloved Constructors, Beloved Anemic Objects 2014-09-01
8 Responsive Layout without CSS media-query 2014-08-22
7 The Modular Homepage Story 2014-07-22
6 Scrum 2010-04-22
5 Scala Considerations 2010-01-12
4 Personal Problems 2008-04-25
3 Fashion 2008-03-12
2 Building Blocks 2008-03-07
1 Things Are Changing 2008-02-26

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: