The Search Bubble Objectified: Modularizing your Javascript

In my last post I introduced a component – a search bubble. This is a nice small component. But it’s not really reusable. Firstly, there are embedded strings all over the place – most notably the searchBox ID. Secondly, I’m polluting the DOM with my code, which means I can call the functions without the context that goes along with them – something that is a bad idea in more complex applications. Let’s see about making a reusable component out of our search box by turning it into a class.

why-modularize

Firstly, a concept that took me FOREVER to grasp.

A Javascript class is a function.

I know – all you Javascript aficionados out there are probably shaking their heads at my obtuseness. But that is a fairly fundamental thing that you have to wrap your head around and it’s problematic if you come from Java or C# or any other object-orientated programming language. I had this written on a nice sticky note stuck to my screen for a while. Let’s create a class!

var My = My || {}

My.Searchbox = function(id) {
  this.id = id;
}

I’ve gone back to basics here and just created a class with an ID. When dealing with a class, the this variable is “the current instantiation of the class” – something fairly specialized. I’ve got most of the code for my class from my last post. I just need to re-factor it for the class syntax, so I’ll just slot it in now.

My.Searchbox = function(id) {
  this.id = id;
  this.searchBoxId = 'searchBox';
	
  this.ClickHandler = function(evt) {
    var elem = $(evt.currentTarget),
        id = 'searchBox',
        CreateDiv = My.Utils.CreateDiv;
		
    // Find out how many search boxes there are
    //   -> More than 1, then destroy them
    if ($('#'+id).length > 0) {
      $('#'+id).remove();
      return;
    }
		
    // Compute the size and position relative to what was clicked
    var left   = elem.position().left - 24,
        top    = elem.position().top + elem.height() + 16,
        width  = 300,
        height = 50;
			
    // If there are no search boxes, then fall through
    // to here and create one.
    var srch = CreateDiv(searchBoxId, 'bubble', left, top, width, height);	
    srch.html("<div class='search'><input type='text' name='search' placeholder='Search'></div>");
  };
	
  $(id).css({ 'cursor': 'pointer'}).click(this.ClickHandler);
};

I can’t just call My.Searchbox.Register(id) to use this any more – that method doesn’t exist. Instead Iinstantiate the class by creating an object with new – it’s this instantiation that actually provides the this variable I have been using to create the object.

$(document).ready(function () {
  var searchBoxHandler = new My.Searchbox('#nav-search');
});

At the beginning, I showed you what the object looked like. If you look at My.Searchbox now in the F12 Developer Tools, you will see that it’s just a function. You can’t call the ClickHandler directly any more – you need to instantiate the object in order to do that.

This code is better, but it’s not reusable yet. That’s because we have the ‘searchBox’ ID that is explicitly scripted. What if I’m using the searchBox ID for something else? What if I want two components? (for example, if this component was a menu instead of a search box, I can have multiple menus). It’s a good idea to create your IDs programmatically. To do this, I reached out to Stack Overflow and found something that created a GUID or globally unique ID. I added this to my My.Utils object like this:

My.Utils = {
  CreateDiv: function(id, cssClass, left, top, width, height) {
    return $("<div id='" + id + "' class='" + cssClass + "'></div>")
      .appendTo('body')
      .css({
        'display': 'block',
        'position': 'absolute',
        'left': left + 'px',
        'top': top + 'px',
        'width': width + 'px',
        'height': height + 'px'
      });
    },
    generateGUID: function() {
      var d = new Date().getTime(),
          guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = (d + Math.random() * 16) % 16 | 0,
                d = Math.floor(d / 16);

            return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
          });

      return guid;
    }
};

Now I can generate the SearchBox ID programmatically:

  this.searchBoxId = My.Utils.generateGUID();

I still have a problem though. My event handler is asynchronous. It doesn’t understand what context it is running in. Basically, this does not exist, so I can’t use this.searchBoxId to access my generated ID. I have to pass in the ID via the jQuery parameterized event. First I alter the registration of the click handler this:

  $(id).css({ 'cursor': 'pointer'}).click({ id: this.searchBoxId }, this.ClickHandler);

Note the object I am passing in as the first argument to click(). This appears in the event handler as evt.data. I can now alter the head of the click handler like this:

  this.ClickHandler = function(evt) {
    var elem = $(evt.currentTarget),
        id = evt.data.id,
        CreateDiv = My.Utils.CreateDiv;

Now my ID is passed into the click handler, everything should work! I’ve still got a couple of “best-practice” things. Firstly, load up the page and press F12 to bring up the developer tools. Instantiate a new search box like this:

var sb = new My.Searchbox('#nav-search');
sb

You will get something like the following:

searchbox-object-1

I can still access the click handler. Worse, I can alter the searchBoxId and id that I’m hooked up to. What I really want to have is read-only elements for the id and searchBoxId and the click handler should be hidden. I don’t want anyone accessing that directly.

To restrict what others can access I need to return the “public interface” for the object. Let’s start by creating what is commonly referred to as a getter – a read-only accessor for the ID. I’m also going to create a read-only accessor for the jQuery object that is the search-box. If the search-box is not visible, I’m going to return null (so I can use !obj.searchBox() to detect visibility). These are going to be defined in the return value of the My.Searchbox function. Anything I don’t want to see publically becomes just a variable in the constructor. So the object now looks like this:

My.Searchbox = function(id) {
  var clickId = id;
  var searchBoxId = My.Utils.generateGUID();
  
  var ClickHandler = function(evt) {
    var elem = $(evt.currentTarget),
    id = evt.data.id,
    CreateDiv = My.Utils.CreateDiv;
    
    // Find out how many search boxes there are
    //   -> More than 1, then destroy them
    if ($('#'+searchBoxId).length > 0) {
      $('#'+searchBoxId).remove();
      return;
    }
    
    // Compute the size and position relative to what was clicked
    var left   = elem.position().left - 24,
        top    = elem.position().top + elem.height() + 16,
      width  = 300,
      height = 50;
      
    // If there are no search boxes, then fall through
    // to here and create one.
    var srch = CreateDiv(searchBoxId, 'bubble', left, top, width, height);  
    srch.html("<div class='search'><input type='text' name='search' placeholder='Search'></div>");
  };
  
  $(id).css({ 'cursor': 'pointer'})
    .click({ id: searchBoxId }, ClickHandler);
    
  return {
    getID: function() { 
      return clickId; 
    },
    getSearchBox: function() {
      var s = $('#'+searchBoxId);
      if (s.length > 0) {
        return s;
      } else {
        return null;
      }
    }
  };
};

Use the F12 Developer Tools on this page to examine our public interface now:

searchbox-object-2

We only have the public interface – two methods – getID() and getSearchBox(). Also, try getting the code for this function from the DOM. It isn’t there. We have our reusable component and you can’t investigate the object (and thus adjust the object) from within the DOM or within some other piece of code.