Putting the Search Box in an ASP.NET vNext Application

Over the last few days I’ve worked on a bubble-styled search box. I started by just getting the HTML and CSS right, together with a little bit of Javascript to pop it up. I then moved it into an object, got it loaded with RequireJS and then turned it into a jQuery component. All of this was done outside of an application. The rapid development cycle that this allows me to achieve is well worth it. Now, however, it’s time to integrate it into my ASP.NET vNext application. You can find the code for this on my GitHub repository, if you like to follow along.

First of all a recap:

  1. Creating the Bubble Search Box
  2. Making the Bubble Search Box a class
  3. Loading the code with RequireJS
  4. Making the Bubble Search Box a jQuery plugin

Now I need to add the jQuery plugin, loaded by RequireJS, to my ASP.NET vNext project.

My first step in this is to add RequireJS to my project. To do this, open up bower.json and edit the dependencies to add requirejs version 2.1.16.

I like to have a standard layout – css in the css directory, javascript in the js directory and so on. So I’m also going to add a section to the exportsOverride section of bower.json. You can check out the directory format once the package restore has happened by expanding the Dependencies node in your Solution Explorer. Right-click on requirejs and use Open in File Explorer. In this case there is just the one Javascript file, but sometimes you will see lots of directories for various things. My resultant bower.json file looks like this:

{
  "name": "WebApplication1",
  "private": true,
  "dependencies": {
    "bootstrap": "3.3.2",
    "jquery": "2.1.3",
    "requirejs": "2.1.16"
  },
  "exportsOverride": {
    "bootstrap": {
      "js": "dist/js/*.*",
      "css": "dist/css/*.*",
      "fonts": "dist/fonts/*.*"
    },
    "jquery": {
      "js": "dist/*.*"
    },
    "requirejs": {
      "js":  "*.js"
    }
  }
}

My next step is to organize my wwwroot/site directory. This contains my code. Firstly, I’m going to move the files that are there into a css sub-directory. I’m also going to create a js directory. My tree now looks like the following:

requirejs-structure-1

I also need to alter my less stylesheet builders within grunt to point to the new locations. My grunt file now looks like this:

/// <binding BeforeBuild='before-build' AfterBuild='after-build' Clean='clean' ProjectOpened='project-open' />

module.exports = function (grunt) {
  grunt.initConfig({
    bower: {
      install: {
        options: {
          targetDir: "wwwroot/lib",
          layout: "byComponent",
          cleanTargetDir: true
        }
      }
    },
    less: {
      development: {
        files: {
          "wwwroot/site/css/site.css": "wwwroot/site/css/site.less"
        }
      }
    },
    watch: {
      files: "wwwroot/site/css/*.less",
      tasks: [ "less:development" ]
    }
  });

  grunt.registerTask("before-build", [ "bower:install", "less:development" ]);
  grunt.registerTask("after-build", []);
  grunt.registerTask("clean", []);
  grunt.registerTask("project-open", [ "watch" ]);

  grunt.loadNpmTasks("grunt-bower-task");
  grunt.loadNpmTasks("grunt-contrib-less");
  grunt.loadNpmTasks("grunt-contrib-watch");
};

Don’t forget to restart the project-open task. Go to the Task Runner Explorer, close the “project-open (Running)” window and agree when it says do you want to terminate it. Then right click on the project-open task and select Run.

My PageLayout.cshtml file also needs to be changed to support RequireJS. I am removing the references to jQuery and Bootstrap and replacing them with a call to RequireJS. I’ve also altered the path to site.css at the same time:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <link href="~/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
  <link href="~/lib/bootstrap/css/bootstrap-theme.min.css" rel="stylesheet" />
  <link href="~/site/css/site.css" rel="stylesheet" />
  <title>@ViewBag.Title</title>
</head>
<body>
  <header>
    <nav>
      <ul>
        <li id="nav-appstore"><span class="glyphicon glyphicon-th"></span></li>
        <li id="nav-notifications"><span class="glyphicon glyphicon-comment"></span></li>
        <li id="nav-search"><span class="glyphicon glyphicon-search"></span></li>
      </ul>
    </nav>
  </header>

  @RenderBody()
  
  <script src="~/lib/requirejs/js/require.js" data-main="site/js/init"></script>
</body>
</html>

All I need now is a site/js/init.js file to complete the initial work to wire things up as they were before, but with RequireJS.

require.config({
  baseUrl: '/',
  paths: {
    'jquery': 'lib/jquery/js/jquery.min',
    'bootstrap': 'lib/bootstrap/js/bootstrap.min'
  },
  shim: {
    'bootstrap': { deps: ['jquery'] }
  }
});

requirejs([
  'jquery',
  'bootstrap',
], function ($) {
  $(document).ready(function() {
    // My Initialization Code here
  });
});

Note that the path to the jQuery and Bootstrap libraries are relative to the wwwroot, not relative to the project. I made this mistake on the first run-through as I was writing this article and I’ve made it several times as I’ve been writing ASP.NET vNext applications. In this case, the baseUrl of / refers to the wwwroot directory.

As an aside, I had a mis-capitalization inside the bower.json file. This resulted in jQuery having a capitalized Q. This wasn’t a problem if you followed through on that naming convention everything except under Dependencies. There I saw jquery (under bootstrap) was not installed, but was installed at the top level. If this happens to you and it bugs you then you need to correct the capitalization, delete the wwwroot/lib directory and re-generate it by running the bower task in the Task Runner Explorer. Once you do this the capitalization is corrected throughout and you don’t have a warning in the Dependencies tree about missing jquery.

At this point you can run the code. When you run it, bring up the F12 Developer Tools and select the Network tab. Ensure the Red record icon is showing. if it’s a green triangle, click it to get recording. Then reload the page. You should see the various pieces – including jQuery and Bootstrap javascript files – loading. If they don’t, you have done something wrong here.

I don’t like the styling on my little menu any more. I’m going to fix that and add the following to my header.less file:

  > nav {
    > ul {
      display: block;
      font-size: 24px;
      color: #f0f0f0;
      padding-top: 8px;

      > li {
        display: inline;
        padding: 4px 16px;
      }
    }
  }

My header bar now looks pretty good. Let’s integrate that search box functionality I created in the last four posts. To do this I have to do three things:

  1. Add the searchbox.js and utils.js files to my project
  2. Set up some styling for the search box
  3. Add the searchbox initiator call into the init.js file

For the first item I just included the files into my wwwroot/site/js directory – they are copied without modifications. For the second item, I’ve created a searchbox.less file in wwwroot/site/css with the following contents:

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

  &: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;
  }

  .search {
    background: data-uri('../img/search-32.png') no-repeat;
    padding-left: 40px;
    height: 32px;
    margin-top: 8px;
    margin-left: 8px;

    &: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;
    }

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

This is a basic use of LESS. it produces a wrapped set of element definitions in CSS. I can do more to simplify this (and probably will at some point). However, for now I just want to get the search box working. I need to copy over the search-32.png file to the wwwroot/site/img directory that I just created. I also need to include this searchbox.less file in the site.css, so edit site.less and include the following line at the bottom:

@import (less) "searchbox.less";

The watcher will regenerate the site.css file at this point. If it doesn’t use the Task Runner Explorer to run the less task.

The final task is to add the search box initiator to our init.js file. There is one wrinkle to deal with. When I created the searchbox.js file, it depended on utils.js with a very specific path called ‘components/utils’. I need to handle that the same way RequireJS handles jQuery – by adding an entry with the new name and path into the paths object. This is what the init.js file looks like now:

require.config({
  baseUrl: '/',
  paths: {
    'jquery': 'lib/jquery/js/jquery.min',
    'bootstrap': 'lib/bootstrap/js/bootstrap.min',
    'components/utils': 'site/js/utils',
  },
  shim: {
    'bootstrap': { deps: ['jquery'] }
  }
});

requirejs([
  'jquery',
  'site/js/searchbox',
  'bootstrap',
], function ($) {
  $(document).ready(function() {
    $('#nav-search').mySearchBox();
  });
});

If you don’t run with no caching already (and you should do this during development), you will probably need to refresh. It isn’t perfect – the positioning is off, for instance. However, it does pop up a search box and allow you to type in it.