Adding a Pop-up Search Bubble to your Web Application

You may have guessed this based on my recent posts, but I’m writing a web application. Some of my stuff is in ASP.NET and some is client-side. This is one of those client-side things. I want to be able to create a pop-up menu. There are a lot of resources out there for creating menus. However they all pretty much have one thing in common. They show you how to create the menu with in-line HTML. In other words, you have to know what your menu is going to look like. In any reasonably complex application this is going to create an explosion of additional stuff in your HTML. What if you have a modular system or role-based access controls and your menu structure comes from a REST call? What I am going to do in this article is show one way to get all this using jQuery.

Let’s start with the basics. I’ve got a web page with the following contents:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>My Test Page</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
    <link href="site.css" rel="stylesheet">
</head>
<body>
    <header>
        <ul>
            <li id="nav-search"><span class="glyphicon glyphicon-search"></span></li>
        </ul>
    </header>
    <script src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
    <script src="site.js"></script>
</body>
</html>

This is a part of a bigger project, but I push functionality like this out to its own set of files while I am developing it. When I’m happy with it, I’ll merge the functionality back into my main application later. All I’ve done here is create a search icon. You can actually run this file (I use Chrome in general for development as I prefer the developer tools, but many people use Firefox and Firebug or Internet Explorer). I do need a little bit of initial styling so that it looks like a toolbar, so I’ve placed the following in the site.css file:

html, body {
  width: 100%;
  height: 100%;
  margin: 0 !important;
  padding: 0 !important;
}

header {
  width: 100%;
  height: 50px;
  background-color: #666666;
  color: #f0f0f0;
}

header ul {
  display: block;
  list-style: none;
  padding-top: 8px;
}

header ul li {
  display: inline;
  font-size: 28px;
}

I’ve also got an empty site.js file. Again – run what you have and make sure it looks good – I do this often as I go along.

Now that I have all the pre-work done, Let’s get down to our task at hand – making a pop-up search box. I like to create a short list of things I have to implement in order to accomplish my desired functionality. In this case:

  1. Click on the Search button
  2. Make a DIV with content appear when I click on the Search button
  3. Make the DIV disappear if I click on the search button again
  4. Style the DIV so it looks like a talk bubble

As a design goal, I want to create an object for my functionality that I can take the component elsewhere via cut-and paste and minimize code that can be mixed up with other stuff.

My first step is to create an object for my functionality. I’ve got a very good book called Javascript Patterns by Stoyan Stefanov and Chapter 5 covers a pattern called the Namespace Pattern. I highly recommend reading what this book has to say about object creation patterns. I’m going to simplify things in the interest of time – this is not an article on the best way to create objects.

var My = My || {};

My.Searchbox = {
  Register: function(id) {
    $(id).css({ 'cursor': 'pointer' })
         .click(My.Searchbox.ClickHandler);
  },
  
  ClickHandler: function(evt) {
    alert("You clicked Me!");
    evt.stopPropagation();
  }
};

$(document).ready(function() {
  My.Searchbox.Register('#nav-search');
});

Let’s take a moment to analyze what I have done here. I’ve created a namespace My (using one of the easier patterns from the book). I’ve then created my Searchbox object – this is accessed using normal Javascript dotted notation – My.Searchbox. I’ve created two functions in this object – one called Register that takes an id, and one called ClickHandler that takes an event. In the Register function, I’m adding a bit of CSS to the passed-in id that changes the cursor to something that indicates it is clickable and I’m adding a click handler that is the other function in my object. I’m just alerting that we got here in the ClickHandler.

The other section of the file is the standard jQuery startup method. It basically says “when the DOM is ready for me, run this function”. In my case, I’m calling my Register method with the ID of the search icon. Run this and two things should happen. Firstly, when you hover over the search icon it should look clickable. Then, when you click on it (or – on a touch screen – touch it), you should see an alert. So far, so good. Item number 1 on my desired functionality has been done. Let’s move onto number 2 and number 3.

I like to have a little logic in my brain before I start writing code. In this case the pop-up bubble doesn’t exist yet, so I’m going to have to create it. However, if it already exists, then that means I’ve already clicked on the search icon, so I want to delete the pop-up bubble instead. My logic is something like “check to see if the pop-up bubble exists – if it does, then remove it, otherwise create it” – your standard if-then-else block. I’m expecting to have to make a bunch of these DIV objects, so let’s create a utility method for this in another object. This allows me to re-use it for any DIV I might need to create. I’ve added the following section to my site.js file:

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'
      });
  }
}

Here I’ve created a new method for creating a DIV. It creates the HTML (a DIV with an ID and a class that are passed in), appends it to the BODY area of my HTML document and then explicitly sets the position and size of the object. This isn’t perfect code. Explicitly, if you specify a left/width or a top/height that would place the DIV outside of the viewport then you won’t see your DIV. I return the created object so that whatever creates it can continue using it (perhaps to add more content or style). Let’s add a small amount of style to the site.css file so we can see our work:

.bubble {
  border: 1px solid blue;
  background-color: blue;
  color: white;
}

Also, let’s adjust the ClickHandler – I want to actually see a box appear:

  ClickHandler: function(evt) {
    var clicked = $(evt.currentTarget),
        CreateDiv = My.Utils.CreateDiv;
    
    CreateDiv('searchBox', 'bubble', 80, 100, 300, 100);
    
    evt.stopPropagation();
  }

Running the result should create a nice blue box on the screen when you click the search icon. It isn’t in the right place and it’s the wrong shape. It also doesn’t go away when you click on the search icon again. Let’s start by making it go away when you click again. In jQuery, the result of a selector is an array. Using the Javascript console in developer tools I can type in any valid javascript. My ID is ‘#searchBox’, so I can type $('#searchBox') in the Javascript console and see the object. Do it before you click on search icon and after. Arrays have a length, so I can use $('#searchBox').length to determine if the search box is visible. This allows me to decide whether to create it or destroy it:

  ClickHandler: function(evt) {
    var clicked = $(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;
    }
    
    // If there are no search boxes, then fall through
    // to here and create one.
    CreateDiv('searchBox', 'bubble', 80, 100, 300, 100);    
  }

Now you can click on the search icon and the blue box will appear or disappear. Before I can call Items 2 and 3 done I need to have a search box inside of my DIV:

    // If there are no search boxes, then fall through
    // to here and create one.
    var srch = CreateDiv('searchBox', 'bubble', 80, 100, 300, 100);  
    srch.html("<div class='search'><input type='text' name='search' placeholder='Search'></div>");

That about covers it for items 2 and 3 – I’ve got my search box appearing and disappearing and there is an actual search box inside it. Now let’s move to item 4 – styling. First of all the box isn’t in the right place – it’s probably not the right size either. I have no idea where my designer will place that search icon eventually so I’m going to have to calculate the location. I may have to adjust the size, but for right now, I’m going to guess. I can always use the F12 Developer Tools to adjust the CSS later and then copy the results back into my JavaScript. jQuery provides .position() and several size methods for getting the position and size of a DIV. My CreateDiv method has positioning and size information passed to it. I’ve pretty much ideally set things up for this:

    // Compute the size and position relative to what was clicked
    var left   = clicked.position().left - 24,
        top    = clicked.position().top + clicked.height() + 16,
      width  = 300,
      height = 50;
      
    // If there are no search boxes, then fall through
    // to here and create one.
    var srch = CreateDiv('searchBox', 'bubble', left, top, width, height);  

Again, this is not ideal. What if the search icon was flush with the left side of the screen? Then the left side of the search box would be off the screen. What if the search icon was on the footer or (more commonly) on the right hand side of the screen? All of these are easily handled once you have the basic case done. It’s just a question of calculating the right position for the box.

Now that I have the right position, I want to style the box – I like a border with a drop shadow and curved boxes. I also like a search icon before the box and no border on my search box. This is “just style”, but I’m not a CSS guru. The way I do this is to mess with the style in the developer tools until I get it right and then I cut and paste it into the site.css file. I always have problems with drop shadows, but there are some great references – my favorite is Microsofts MSDN article. I downloaded my search icon from iconfinder.com. This is what I came up with:

.bubble {
  background-color: white;
  border: 1px solid #C0C0C0;
  border-radius: 8px;
  box-shadow: 2px 2px 2px 2px gray;
  color: white;
}

.search {
  background: url(search-32.png) no-repeat;
  padding-left: 40px;
  height: 32px;
  margin-top: 8px;
  margin-left: 8px;
}

.search input {
  font-size: 18px;
  border: 0;
  margin-top: 2px;
  color: #666666;
}

Now that I have a basic pop-up, I want to make it a “chat” bubble. I know – I teased about this in the title and it’s taken me this long to get around to it, but it’s really the last thing to do. In order to make it a chat bubble, I need to add a CSS3 shape. There is a huge list of CSS shapes and css-tricks.com has a habit of showing off these sorts of tricks. In this case, they have a demo on what you can do. I want to position an up triangle in the right place on my search box right under the search icon that is activating the box. To do this I need to use the :before selector in CSS3 for this. As I always do, I write something basic to get the selector into the F12 developer tools, then tweak it and finally copy it back to the file. I started with this:

.bubble:before {
  content: '';
  width: 0;
  height: 0;
  border-left: 10px solid transparent;
  border-right: 10px solid transparent;
  border-bottom: 10px solid red;
}

And my final CSS:

.bubble:before {
  content: '';
  width: 0;
  height: 0;
  position: absolute;
  left: 20px;
  top: -16px;
  border-left: 16px solid transparent;
  border-right: 16px solid transparent;
  border-bottom: 16px solid #C0C0C0;
}

This is almost there. I wanted an outline though, not a solid triangle. This isn’t directly supported, but I’ve got two elements – the bubble outer DIV and the search inner DIV. I can use the search inner DIV to do the same thing in white, like this:

.search:before {
  content: '';
  width: 0;
  height: 0;
  position: absolute;
  left: 22px;
  top: -14px;
  border-left: 14px solid transparent;
  border-right: 14px solid transparent;
  border-bottom: 14px solid white;
}

This is what the resulting search box looks like:

search-box

Now I’m ready to integrate this functionality back into my main program.