Screen Shot 2013-05-16 at 11.19.16 PM

Introduction

This tutorial is based on an older work of mine that I did couple months ago. It’s a relatively simple search function that iterates through a given array of possibilities, and returns a set of result.
Since the purpose is to walkthrough functions and building of a custom search field, I shall not make this tutorial longer than it should by explaining about the whole other styling and structure.

The Idea

Data is feed through server, and then translated into JSON – A collection of data items that you wish to display. But to keep the experiment simple, I went back to basic entries using country names instead.
As you search, the script should take your keywords and match or find resemblance of it within the array of country names. E.g. typing “bah” should return you two results – “Bahamas”, “Bahrain”, while typing “baha” should return you one result – “Bahamas”.
But to make this experiment interesting, I’m throwing in jQuery animations and a little styling to make it fancy.
So in a short list, here’s the features we are aiming to achieve:

  • Filtering through an array using keywords typed by user.
  • Results should respond and updates instantly as the user types along.
  • User should be able to configure some portions of the settings.
  • On top of it, there should also be a set of default settings.

HTML Structure

It’s a simple structure, and results are modified using jQuery.

1
2
3
4
5
6
<div id="main">
  <form id="form">
    <input type="text" id="searchBox" class="radius" placeholder="Search your favourite country..." />
    <input type="button" id="searchButton" class="radius" value="Search" />
  </form><!-- end of #form -->
</div><!-- end of #main -->

CSS Styles

The CSS styles should be pretty straight-forward, nothing noteworthy of so I shall avoid explaining in this section.

The Search Box

 

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
#main #form {
  position: relative;
  margin: 10px auto;
  padding: 20px;
  width: 500px;
  height: 100px;
  border: 1px solid #eee;
}
#main #form #searchBox {
  position: absolute;
  top: 50px;
  left: 100px;
  padding: 10px 20px;
  height: 10px;
  width: 210px;
  border: 2px solid #ccc;
}
#main #form #searchBox:focus {
  border: 2px solid lightblue;
 
  -webkit-box-shadow: 1px 1px 10px lightblue;
  -moz-box-shadow: 1px 1px 10px lightblue;
  box-shadow: 1px 1px 10px lightblue;
}
#main #form #searchButton {
  position: absolute;
  top: 50px;
  left: 360px;
  padding: 0 10px;
  height: 34px;
  border: none;
  background: #444;
  color: #82d7d0;
 
  text-shadow: 1px 0px 0px #222;
  cursor: pointer;
}
#main #form #searchButton:focus {
  color: #444;
  background: #82d7d0;
  text-shadow: 1px 0px 0px #eee;
}

The Search Fields

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#resultWrapper {
  position: absolute;
  background: #eee;
  padding: 10px 0 20px 0;
  max-height: 390px;
  width: 250px;
  border: 1px solid #777;
  overflow: hidden;
}
#resultWrapper h4 {
  padding: 2px 5px;
  background: #777;
  color: #eee;
}
#resultWrapper ul li {
  list-style: none;
  padding: 10px;
  height: 17px;
  border-bottom: 1px dotted #ccc;
  cursor: pointer;
}
#resultWrapper ul li:hover, #resultWrapper ul li:active {
  background: #fbf9ea;
}

Javascript Structure

Here comes the good and fun part. We need to determine a couple of things before we get started. Because we don’t want to see a huge nest of functions and declaratives all over the place, we want to obtain a certain structure that can be easily understood, behaviors or events consolidated in the right function, and features are properly scoped separately from one another.

So after giving some thoughts, these are the functions/objects we’re gonna create:

  • filtering(), the function that does the filter through array.
  • results(), the function that animates, destroy, display or hide the results.
  • changeStyles(), a style changer function for any elements.
  • init(), the initializer of the whole script. Mostly to contain behaviors and events.
  • defaults{}, an object holder of all default settings.
  • settings{}, an object holder for a merged settings from defaults and user defined.

Additionally, we’ll declare a set of variables that holds references to jQuery selectors. This is so that we don’t repeat long codes and having to change everywhere when elements change, and also faster performance since jQuery don’t have to find the selectors again each time you call.

So, that’s it! We have an idea of how we want to scope and structure our functions, now let’s take it further. To start it off, we shall do the basic ones first.

Defaults and References

 

1
2
3
4
5
6
7
8
9
10
11
12
13
var searcher = this,
        // default settings       
        defaults = {
          $searchBox: $('#searchBox'),
          position: {}, // to hold the position of the parent Node that we are appending to
          styles: {}, // allowing users to define their own styles for the results container
          defaultHighlightRow: {background: 'none', color: '#444'}, // allowing users to define the highlighted styles in result container
          speed: 'fast',
          searchList: [
            'Afghanistan','Albania','Algeria','Andorra','Angola','Antigua and Barbuda','Argentina','Armenia','Australia','Austria','Azerbaijan',    'Bahamas','Bahrain','Bangladesh','Barbados','Belarus','Belgium','Belize','Benin','Bhutan','Bolivia','Bosnia and Herzegovina','Botswana','Brazil','Brunei','Bulgaria','Burkina Faso','Burma','Burundi'
          ]
        }
  };

As you’ve seen above, most of them are just default settings in case user doesn’t define their own. Without a defined setting, our functions have nothing to process. The most significant property to take note is “searchList”. This is the key to the whole search script to work. It contains the array of data to filter through. And of course, this is a property that our user can define on their own outside of this script. We’ll touch on that later.

Now, let’s proceed on to declaring the references. This shouldn’t be too difficult.

1
2
3
4
5
// pre-defining var for common selectors
var   $container = $('#container'),
        $main = $('#main'),
        $searchBox = (defaults.$searchBox) ? defaults.$searchBox : search.option.$searchBox,
        $resultWrapper = $('<div id="resultWrapper"></div>');

This should be pretty straight forward, except for the shorthand code. For those who hasn’t come across it, this is a shorthand “if-else” statement.

1
(defaults.$searchBox) ? defaults.$searchBox : search.option.$searchBox

the () defines the condition, and ? contains the code where “if” the condition is satisfied, while : contains the code for “else”.

We then need a method to combine both user define settings and the default settings. To make things easier, jQuery has already provided a handy one as shown below.

1
2
3
// extend and combine the settings provided by user and defaults
searcher.settings = {}; 
if($.isPlainObject(options)) searcher.settings = $.extend(defaults, options);

we first define the “settings” property so we are sure it’s a new instantiation and emptied. Then a condition to ensure that whatever the user passes into the function, is indeed an object before doing the merge.

The Filtering() Function

As mentioned above, there’s only 4 main functions that is going to power the entire search script. Let’s go through them one by one, beginning with filtering().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
searcher.filtering = function(e) {
      var total = 0,
          x,
          keyword = $searchBox.val(),
          results = {
            header: '<h4>Possible results</h4>',
            msg: '<ul id="results">'
          };
 
      if(keyword) {
        for(x = 0; x < defaults.searchList.length; x++) {
          if(defaults.searchList[x].toLowerCase().match(keyword.toLowerCase())) {
              results.msg += '<li>'+defaults.searchList[x]+'</li>';
              total++;
          }
        }
        results.msg += '</ul>';       
        searcher.results(results,total,'show');
      } else {
        searcher.results(null,null,'hide');
      }
};

The variable “results” is kind of a template for showing the results. It contains two properties that separate the header element from the result list container, to maximize customization.

We then first check to know if the user has typed any keywords into the search field – If no keywords we should hide away the results container, else we shall grab the keywords and compare with our search array. The method we’re going to use is the simple javascript “match()” and that should be enough to perform the basic tasks of a search function. Since “match()” also accepts RegExp (Regular Expression), it allows advanced customization for search queries.

The Results() Function

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
searcher.results = function(result,total,showOrHide) {
      if(result) {
        if($.isPlainObject(result)) {
          $(resultWrapper).html(result.header + result.msg);
        } else {
            $resultWrapper.html(result);
        }       
        // get the actual padding of each row
        var rowHeight = $('#results li').innerHeight();
        // animate so it looks smooth when adjusting height
        $resultWrapper.animate({
          height: total * rowHeight + 15
        });         
      } else {
          $('#results li').off();
          $resultWrapper.html(' ');
      }
 
      if(showOrHide == 'hide') {
          $resultWrapper.fadeOut();
      } else {
          $resultWrapper.fadeIn();
      }

Next up, we’re going to write up the function that shall process and display the results container. Let’s go through the first part of the function, as shown above.

If any results was passed into the function, and also comply to the plain object criteria, we will just inject the header and message directly into the body of our results container.

We then grab the height of each result, so that we can adjust the height of the results container. This is because, the container should not have a fixed height so it can expand and contract as it requires. To make it fancier, we throw in a simple animation of its height.

If no result has been passed in, we will first remove all attached event listeners and then the contents within its body.

Let’s complete the rest of the function, as shown below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
      // binding the different mouse events to the search field
      $('#results li').css(searcher.settings.resultsRow).on({
          click: function() {
            var selected = $(this).html();
            $searchBox.attr('value',selected);
            searcher.results(null,null,'hide');
          },
          mouseover: function() {
            $(this).css(searcher.settings.highlightRow);
          },
          mousedown: function() {
            $(this).css(searcher.settings.highlightRow);
          },
          mouseout: function() {
            $(this).css(searcher.settings.defaultHighlightRow);
          }
      });
};

This should also be pretty straight forward. Basically, when we allow users to customize the looks of it, we need to apply it. This section primarily toggles on and off the styles that user defines. To make that happen, we attach three events to each of the results in the container – “Click”, “Mouse over” and “Mouse out”.

The “Click” event takes the value of what the user has clicked, and replace it on the search field. This fulfills the automated completion of a search function.

The “Mouse over” toggles the style to reflect when the user hovers over the result in the container.

The “Mouse out” toggles away the style and set back to default. Catching up so far? Well then, we’re good to go! Let’s move on.

The ChangeStyles() 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
searcher.changeStyles = function(node,settings) {
      if(settings && $.isPlainObject(settings) && node) {
        switch(settings) {
          case settings['box-shadow']:
            node.css({
              '-webkit-box-shadow': settings['web-shadow'],
              '-moz-box-shadow': settings['web-shadow'],
              'box-shadow': settings['web-shadow']
            });
            delete settings['box-shadow'];
          break;
          case settings['border-radius']:
            node.css({
              '-webkit-border-radius': settings['border-radius'],
              '-moz-border-radius': settings['border-radius'],
              'border-radius': settings['border-radius']
            });
            delete settings['border-radius'];
          break;
          default:
            node.css(settings);
          break;
        }
      } else {
        $.modalDialog('error!')
      }
};

This is a simple function that handles most of the change styles we need. To be honest, you never really needed this function. I did it because I dislike the idea of repetitive code floating around the script. The idea is to simply do automated styling just by passing in the right string, and applying it with a switch case.

The Init() 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
// private method within the plugin
var init = function() {
     console.log('initializing search()');
 
     $.place($resultWrapper, $('#form'), function(hasPlaced) {
          var thePos = $searchBox.position();
 
 
          defaults.position = thePos; console.log(defaults.position);
 
          var width = $searchBox.width(),
                    height = $searchBox.innerHeight();
 
          changeStyles($resultWrapper,searcher.settings.styles);
 
          $resultWrapper.css({
               top: defaults.position.top + height,
               left: defaults.position.left
               }).hide();
     });
 
     // attaching main events to DOM elements
     $searchBox.on({
          keyup: function(e) {
               searcher.filtering(e);
          },
          blur: function() {
               setTimeout(function() {
                    searcher.results(null,null,'hide');
               },200);
          }
     });
 
     $(document).keydown(function(e) {
          if(e.keyCode == 27) {
               $searchBox.attr('value', '');
               $searchBox.blur();
          }
     });
};

This function simply initializes and setup the proper behavior before the user gets to interact with the field. Basically, it’s a calculation of the search field’s position so that we know where exactly to display the results list. And finally, we attach the appropriate events “keyup” and “blur” to the search field. One additional thing I’ve included below is to detect the “esc” key, which is equivalent to keyCode 27. It does just exactly the same thing as blur event, except that it empties the field as well.

1
2
3
4
/************************************
// Call your methods which you require to initialize the plugin
************************************/
init();

Last but not least, we call the method init() to start the script.

In your index.html…

Head on over to your index.html file and copy this code in.

1
2
3
4
5
<script type="text/javascript">
$(document).ready(function() {
     $('#container').searcher();
});
</script>

Alternatively, if you wish to customize using the methods and settings capabilities you’ve setup above, you can test it out. Follow the example below to get a rough idea of how you can pass in an object of styles and settings:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script type="text/javascript">
$(document).ready(function() {
     $('#container').searcher({
          // example of defining your own styles
          // for the results container
          styles: {
               border: '1px solid #777',
               'border-radius': '10px',
               'box-shadow': '1px 1px 10px #333',
               background: '#eee'
          },
          // example of defining your own styles
          // for each mouseover/selected result rows
          highlightRow: {
               background: '#fbf9ea'
          },
          // example of defining your own styles
          // for each result rows
          resultsRow: {
               'border-bottom': '1px dotted #ccc'
          }
     });
});
</script>

With that, the plugin is complete! You should see it running and responding properly with the right results! If you can get it right this far, you’re pretty much done! It’s not really that complicated for a simple search function like this.

That’s it!

If you can get it right this far, you’re pretty much done! It’s not really that complicated for a simple search function like this. I hope you enjoyed this tutorial and build your first search function soon! And please do not hesitate to ask me any questions should you have. I’ll be glad to answer them!