Plugin to Data Binding with jQuery

Tim Kulp | March 16, 2011

 

Today’s internet browser is a powerful application engine running the software of tomorrow. Data is flowing to the browser from all directions (online, local storage, etc…) with robust options for storage and retrieval. Unfortunately, JavaScript still lacks an easy way to display or interact with this data. In this article we will explore implementing a simple client side data binding solution using a jQuery plugin that will allow users to sort, search and bind the data to a template.

Thinking about data binding

Data binding requires three things, data (obviously), a view (what we want the end result to look like) and logic to control the interaction of the view and the data. For this article we are going to examine client side data binding with the Browser-side Template Design Pattern. There are other options, but this is most ideal for a reusable solution. (Check out information about another strategy, The HTML Message Pattern)

The Browser-side Template (BST) Pattern separates the data from the view (in this pattern the View is called a Template) by having only the data come from the data source. Using the BST pattern an Ajax call to a Web Service would only return the data we want to display and let HTML handle what the template looks like while JavaScript handles how to bind the data. This allows designers to create the template in their favorite Web Interface GUI (Expression Web, DreamWeaver, etc…) without having to get your development team involved. BST also provides for simple maintenance to the template if the layout needs to change because you will not have to sift through string concatenation to rebuild the HTML.

Setting up our Data

Using BST we need to return pure data from the data source, but what format will the data be in? When dealing with JavaScript you have two main data formats: JSON (JavaScript Object Notation) and XML. If you are not familiar with JSON, then you are really missing out on a great data format. Check out JSON.org for details about formatting and usage. Any data source that can return text can return JSON. Text files, Web Services, cookies, client side storage objects are all great candidates for providing JSON to the browser. In this article we are going to focus on JSON data due to its small size (compared to XML) and ease of use. For our example we are going to use the following data:

[
{"ID":7,"FirstName":"Peter","LastName":"Steale","Email":"psteale@test.com"},
{"ID":8,"FirstName":"Walter","LastName":"Williams","Email":"wwilliams@test.com"},
{"ID":9,"FirstName":"Zeno","LastName":"Altman","Email":"zaltman@test.com"},
{"ID":6,"FirstName":"Brooke","LastName":"Vailen","Email":"bvailen@test.com"},
{"ID":5,"FirstName":"Angie","LastName":"Donaldson","Email":"adonaldson@test.com"},
{"ID":4,"FirstName":"Samantha","LastName":"Jones","Email":"sjones@test.com"},
{"ID":1,"FirstName":"Bill","LastName":"Captain","Email":"bcap@test.com"},
{"ID":2,"FirstName":"Chris","LastName":"Jones","Email":"cjones@test.com"},
{"ID":3,"FirstName":"Peter","LastName":"Arnold","Email":"parnold@test.com"}
]

In our example data, we have nine person objects that are composed of the properties: ID, First Name, Last Name and Email. Save this into a text file called data.json.

Setting up our Template

Now that we have the data we will be binding we need to consider how it is going to appear on the screen. As a web developer when I think about data binding, I think about two things: the container object and the template that gets populated with the data. For an ASP.NET GridView, the container is an HTML table (<table>) with the template being a table row (<tr>) with a series of columns (<td>) that hold the data.

For our example we are going to build our data binder to draw out a series of cards. These cards emulate business cards, an interface that we are all accustom to dealing with outside of the digital world. Our end product will look like the following:

Each card will list the last name, first name and email address with a link to view more details about the person.

First we need to add some HTML placeholders to the document. Add the following in the <body> tag:

<div id="data-container"></div>
<div id="pager-container"></div>

The <div id="data-container"></div> will hold all the cards that we create while the <div id="pager-container"></div> will hold the paging controls. These <div>s will be populated with content by our JavaScript data binding logic.

Now we need the template. Place the following directly below the pager-container object (note: this can go anywhere in your <body> but I recommend keeping all three of these items together for easy reference later).

<script id="template" type="text/html">
        <ul class='data-item'>
            <li class='person-name'>
                #LastName, #FirstName
                <input type='hidden' id='hdnID#ID' value='#ID'/>
            </li>
            <li>
                <label>Email: </label><span class='person-email'>#Email</span>
            </li>
            <li>
                <a href='#’ class=’cmd-button’  onclick=’lnkDetails_Click(#ID);’>Details</a> 
            </li>
        </ul>
    </script>

We wrap the template in a <script> tag so that it does not render to the visible HTML on the page. Notice that we are using #PROPERTY_NAME as the place holder for the property value in the template. The template will build out the card to be an unordered list but just as easily could be a table, a bunch of <div> tags or whatever you want to design.

Your completed <body> should look like the following:

<body>
    <div>
        <div id="data-container"></div>
        <div id="pager-container"></div>
    </div>
    <input type="button" id="btnBind" value="Bind" onclick="btnBind_Click(this);" />
    <script id="template" type="text/html">
        <ul class='data-item'>
            <li class='person-name'>
                #LastName, #FirstName
                <input type='hidden' id='hdnID#ID' value='#ID'/>
            </li>
            <li>
                <label>Email: </label><span class='person-email'>#Email</span>
            </li>
            <li>
                <a href='#’ class=’cmd-button’ onclick=’lnkDetails_Click(#ID);’>Details</a> 
            </li>
        </ul>
    </script>

</body>

Data binding in Plain Old JavaScript

Using JavaScript we can fulfill the three elements of using the BST Pattern: Get Data, Build a Template and Populate that Template. We already saw the HTML we are going to use to build the template so now we just need to get the data and bind it to the template.

Getting the data

Earlier in the article we created the data.json file and saved it to our web project. Now we are going to use Ajax to grab the contents of that file for processing. The data in the file is a JSON array, but JavaScript cannot natively parse a JSON array to ensure that the data is in the correct format. We will need a little help from JSON.org and their JSON.parse script. Simply download the script and add a reference to the head of your document (remember to copy it locally to avoid script injection attacks):

<head>
    <title>Data binding with Plain Old JS</title>
    <script src="Scripts/JSON.js" type="text/javascript"></script>

Now we will setup the Ajax call to the data.json file with the following JavaScript. When the user clicks the btnBind button, this Ajax call will retrieve the data and display it on the page.

First, we need to setup the variable that will hold the Ajax request:

<script language="javascript" type="text/javascript">
function btnBind_Click(e) {
///<summary>This is the event handler for a button on the page that calls the data to be bound</summary>
       ///<param name="e" type="Event">Information about the Event that triggered the function call</param>
       var xmlhttp = null;

       if (window.XMLHttpRequest) {
        xmlhttp = new XMLHttpRequest();
}
       else {
        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}

This block of code creates the function then declares the xmlhttp variable which will hold our Ajax request. The next block of code determines if the browser supports the window.XMLHttpRequest object. This is an important step as not all browsers support Ajax requests using the same object. Modern browsers all support the XMLHttpRequest object natively while legacy browsers (I’m looking at you IE6) need to create an Ajax request using an external ActiveX object.

Now that we have our xmlhttp request object set to the correct format for the browser, we need to setup the event handler for when data comes back from the Ajax request.

xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
        var rawData = JSON.parse(xmlhttp.responseText, null); //return the JSON array                    
var _dataPage = document.createElement("div");
              _dataPage.setAttribute("class", "page-display");
              for (var i = 0; i < rawData.length; i++) { //for each data item, draw the card
var item = rawData[i];
                     var content = document.getElementById("template").innerHTML.trim();
                     for (var property in item) {
                        var find = "#" + property;
content = eval("content.replace(/" + find + "/g, '" + item[property] + "')");
}
_dataPage.innerHTML += content;
}
document.getElementById("data-container").appendChild(_dataPage);
}

}

Any time the readyState property of the xmlhttp object changes, onreadystatechange will fire. Inside the event we check the readyState property to make sure it is complete (4 = complete see readyState Property for more options) and that the HTTP status is successful (200 = successful see status Property for more options). If the request is complete and successful then we use the JSON.parse method to validate that the xmlhttp.responseText string is a valid JSON object.

The next section of code creates a <div> DOM element that will hold our bound templates. For each item in the JSON array, read the template contents (trimming off any extraneous white spaces) and then loop through each property of the JSON object replacing the property placeholder with the property value. The updated content variable is then added to the data page. When looping through the data is complete, the page is added to the data-container object for display.

Finally, we set the address of the data we want to request and call the send() method which executes the Ajax call.

xmlhttp.open("GET", "data/dataFile.json", true);
xmlhttp.send();
}
</script>

So your complete <head> should look like the following:

<head>
    <title>Data binding with Plain Old JS</title>
    <script src="Scripts/JSON.js" type="text/javascript"></script>
    <script language="javascript" type="text/javascript">
        function btnBind_Click(e) {
            ///<summary>This is the event handler for a button on the page that calls the data to be bound</summary>
            ///<param name="e" type="Event">Information about the Event that triggered the function call</param>
            var xmlhttp = null;

            if (window.XMLHttpRequest) {
                xmlhttp = new XMLHttpRequest();
            }
            else {
                xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            }

            xmlhttp.onreadystatechange = function () {
                if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
                    var rawData = JSON.parse(xmlhttp.responseText, null); //return the JSON array                    
                    var _dataPage = document.createElement("div");
                    _dataPage.setAttribute("class", "page-display");
                    for (var i = 0; i < rawData.length; i++) { //for each data item, draw the card
                        var item = rawData[i];
                        var content = document.getElementById("template").innerHTML.trim();
                        for (var property in item) {
                            var find = "#" + property;
                            content = eval("content.replace(/" + find + "/g, '" + item[property] + "')");
                        }

                        _dataPage.innerHTML += content;
                    }
                    document.getElementById("data-container").appendChild(_dataPage);

                }

            }
            xmlhttp.open("GET", "data/dataFile.json", true);
            xmlhttp.send();
        }
    </script>
</head>

As you can see, data binding can be done with JavaScript, but is there an easier way?

jQuery to the rescue

jQuery simplifies many JavaScript tasks by reducing the amount of code we need to write. For example, in JavaScript, if we want to hide a DOM element we would do the following:

document.getElementById("data-item").style.display = "none";

In jQuery, here is the same function:

$("#data-item").hide();

Both code segments set the DOM element to have a style property of display:none; but jQuery saves us some key strokes and provides for cross browser support (which is important when we look at the Ajax call again). Another jQuery enhancement can be found in finding all the DOM elements on the page with the data-item class. Suppose we wanted to count all the elements with a class of data-item to determine how many data items are on the page. If we wanted to find all the child elements of a DOM object with the class data-item in JavaScript we would have something like this:

var nodes = options.boundContainer.childNodes;
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].getAttribute("class") == "data-item") {
        //do some code
}
}

In jQuery:

$(".data-item").each(function (i, item) {
//do some code
});

Using jQuery we can instantly find all the elements with the data-item class without looping through every element and checking the class attribute.

$.tmpl

Not only does jQuery simplify JavaScript tasks, but it has a very robust plugin architecture with a passionate developer community. $.tmpl is a plugin for jQuery that simplifies populating our template to a single line of code. As of now, $.tmpl is a plugin that you need to download separate from the jQuery library, but soon it will be built and ready to use (for more information check out jQuery Templates, Data Link, and Globalization Accepted as Official jQuery Plugins).

Let’s revisit our Ajax call to retrieve the data from data.json using jQuery and $.tmpl. First, add a reference to the jQuery library and the $.tmpl plugin. Make sure you add the jQuery library reference first.

<script src="Scripts/jquery-1.5.1.min.js" type="text/javascript"></script>
<script src="Scripts/jQuery-tmpl.js" type="text/javascript"></script>

Next, we need to update our template to use the placeholder format expected by the $.tmpl plugin.

<script id="template" type="text/html">
<ul class='data-item'>
<li class='person-name'>
${LastName}, ${FirstName}
<input type='hidden' id='hdnID${ID}' value='${ID}'/>
</li>
<li>
<label>Email: </label><span class='person-email'>${Email}</span>
</li>
<li>
<a href=’#’ class=’cmd-button’  onclick='lnkDetails_Click(${ID});'>Details</a>
</li>
</ul>
</script>

Using the $(document).ready event we attach the btnBind object’s click event to an anonymous function. When the user clicks the btnBind DOM element we want the Ajax call to fire so add the following code:

<script language="javascript" type="text/javascript">
        $(document).ready(function () {
            $("#btnBind").click(function () {
$.ajax({
url: "data/dataFile.json",
       type: "GET",
       contentType: "application/json; charset=utf-8",
       beforeSend: function () {
            // Add any logic to display the loading UI
        $("#data-container").hide("slow", displayLoading());
},
       complete: function () {
                // Add any logic to hide the loading UI
            hideLoading();
},
       success: function (jsonData) {
                var jdata = $.parseJSON(jsonData);
                            var page = $("<div/>").addClass("page-display");
                            $("#template").tmpl(jdata).appendTo($(page));
                      $("#data-container").append($(page)).show("slow");
},
error: function (result) {
alert(result.message);
}
});
});
});
</script>

The Ajax call looks a lot different then what we did for regular JavaScript. All of these properties can be accessed through the XmlHttpRequest object, jQuery just makes it a lot easier to configure. Now, we do not need to be checking the onreadystatechange event with codes and id numbers, we can simply bind to events like beforeSend, success, error and complete. The ContentType option of the $.ajax object allows us to state that the expected output of the Ajax call is JSON (you might need to add support for this MIME type).

In the success event, we first parse the data that came back from the Ajax call (jsonData argument of the success event) using the $.parseJSON function which is built into the jQuery library. Next we create a <div> with the $("<div/>") syntax and add the page-display class to the newly created div. With the page variable set, we execute the $.tmpl plugin with the line:

$("#template").tmpl(jdata).appendTo($(page));

This creates a jQuery object from the template, populates the template with the jdata JSON array and then adds the output to the page object. We then add the page to the data-container DOM object so it will appear on the page. The output from the $.tmpl plugin will create an instance of the template for each item in the jdata array with the placeholders (${PROPERTY_NAME}) replaced with the property value. This is a simple solution to do Brower-side Template data binding but is not quite complete.

In the development community we have grown to expect more from a data binding control. Functions like paging, sorting and searching are required to have a complete data binding solution.

Here is the complete code to do the $.tmpl data binding:

<head>
    <title></title>
    <script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>
    <script src="Scripts/jQuery-tmpl.js" type="text/javascript"></script>
    <script language="javascript" type="text/javascript">
        $(document).ready(function () {
            $("#btnBind").click(function () {
                $.ajax({
                    url: "data/dataFile.json",
                    type: "GET",
                    contentType: "application/json; charset=utf-8",
                    beforeSend: function () {
                        // Add any logic to display the loading UI
                    },
                    complete: function () {
                        // Add any logic to hide the loading UI
                    },
                    success: function (jsonData) {
               var jdata = $.parseJSON(jsonData);
                        var page = $("<div/>").addClass("page-display");
                        $("#template").tmpl(jdata).appendTo($(page));
                  $("#data-container").append($(page)).show("slow");
                    },
                    error: function (result) {
                        alert(result.message);
                    }
                });
            });
        });
    </script>
</head>
<body>
    <div>
        <div id="data-container"></div>
        <div style="clear:both;"></div>
        <div id="pager-container"></div>
        <div style="clear:both;"></div>
    </div>
    <input type="button" id="btnBind" value="Bind"/>
<script id="template" type="text/html">
<ul class='data-item'>
<li class='person-name'>
${LastName}, ${FirstName}
<input type='hidden' id='hdnID${ID}' value='${ID}'/>
</li>
<li>
<label>Email: </label><span class='person-email'>${Email}</span>
</li>
<li>
<a href='#’ onclick=’lnkDetails_Click(${ID});' class=’cmd-button’>Details</a>
</li>
</ul>
</script>
</body>

Power up your $.tmpl with $.fn.DataBinder

To enable sorting, paging and searching in a template, we need another plugin. The $.fn.DataBinder plugin fleshes out the $.tmpl plugin with these features. Here is the Ajax success event using the DataBinder plugin:

success: function (jsonData) {
       var jdata = $.parseJSON(jsonData);
var options = {
              data: jdata,
              enablePaging: true,
              pagerContainer: $("#pager-container"),
              pageSize: 6,
        template: $("#template") 
       };
$("#data-container").DataBinder("bind", options).show("slow");
}

We now create an options JSON object that will pass in the necessary parameters. These options define settings for the DataBinder. Here is a description of the settings:

  • data: the JSON array or JSON object to bind
  • enablePaging: whether or not to use paging for your data binding
  • pagerContainer: the jQuery object that will hold the paging controls
  • pageSize: the number of items to show per page
  • template: the jQuery object that holds the template information.

When we call the DataBinder plugin, we pass in the name of the method we wish to execute (bind, search or sort) and the options to support that method. With these settings our $.fn.DataBinder plugin will populate the $("#data-container") with the populated templates displaying six items per page as well as create the paging controls. Finally we chain the show(“slow”) method to display the results in a smooth animation.

Using the Search method allows the user to display only results that match a provided string. The syntax is basically the same with a new option:

success: function (jsonData) {
       var jdata = $.parseJSON(jsonData);
var options = {
              data: jdata,
              enablePaging: true,
              pagerContainer: $("#pager-container"),
              pageSize: 6,
              template: $("#template"),
        searchString: $("#txtSearch").val()
       };
$("#data-container").DataBinder("search", options).show("slow");
}

searchString is the string to use for finding a match in the jdata array. Calling the search method of the $.fn.DataBinder plugin will use the jQuery.grep method to find elements that match the provided string and then returns the matches as an array. We then replace the options.data property with the output of jQuery.grep and pass the options into the bind method. Both, search and sort call bind when they are finished their processing.

Sort is very similar to search but has three new options:

  • sortField: the field in the jdata array to sort on
  • sortDescending: whether the data should be sorted descending or not
  • sortProcessingFunction: the function to use to prepare the data for sorting

Here is the updated success function:

success: function (jsonData) {
       var jdata = $.parseJSON(jsonData);
var options = {
              data: jdata,
              enablePaging: true,
              pagerContainer: $("#pager-container"),
              pageSize: 6,
              template: $("#template"),
              sortField: "LastName",
              sortDescending: false,
sortProcessingFunction: function (a) { return a.toUpperCase(); }
       };
$("#data-container").DataBinder("sort", options).show("slow");
}

Using these settings for the options object, we are sorting by Last Name in ascending order (because sortDescending = false) and making all values Upper Case prior to sorting them so that casing is ignored during the sort process. The sort method simply sorts the jdata array using the JavaScript Array.sort method.

Paging is a bit more challenging than sorting or searching because there is not any built in JavaScript or jQuery features to support this. We need to wire up our own pager controls. In the $.fn.DataBinder plugin, paging is simply the process of making the current page visible and all others hidden.

When paging is enabled and a page size specified, the plugin creates a series of <div> elements in the data-container object (the jQuery object that called the plugin). Each page has the number of items determined in the options.pageSize setting. If the number of records is not even with the page size, then another page is created for the remainder. For example, if you have a page size of 6 and 9 items total, the second page would have 3 items on it. When a page is full (as in it contains the number of items determined by the page size) then it is appended to the jQuery object that called the plugin. Paging is accomplished with the following code:

$.each(options.data, function (i, item) {
if (i > 0 && i % options.pageSize == 0) {
              if (i > options.pageSize)
                _dataPage.hide();

              $(options.template).tmpl(_arPage).appendTo($(_dataPage));
              $(element).append(_dataPage);
              _dataPage = $("<div/>").addClass("page-display");
        _arPage = new Array();
       }
       //append template to the content
_arPage.push(item);
});
_dataPage.hide();

if (options.data.length % options.pageSize != 0) {
    
$(options.template).tmpl(_arPage).appendTo($(_dataPage));
       $(element).append(_dataPage);
}

We loop through each item in options.data and add it to the _arPage array which is an array of the data items that will be bound to the page. If the page size has been reached and the iterator ( i ) is greater than zero, then bind the data to the template (using $.tmpl plugin) and add the output to the _dataPage jQuery object. Add the data page to the jQuery object that called the DataBinder plugin (element), create a new page and clear the page data array for the next round.

Building the pager buttons is built into the bind method if paging is enabled. Each paging button does the same thing but with different implementations: determine which page to show, hide all others and check to see if the paging buttons should be disabled. The complete code is available in the plugin but here is a sample from the “go to first record” button:

var first = _drawPagerButton("«", "pager-first", false, function () {
              $(".page-display").each(function (i, item) {
if (i == 0) {
                     $(item).show("slow");
                     $("#pager-firstItem").text("1");
                     $("#pager-lastItem").text($(item).find(".data-item").length);

                     // Disable First/Prev, Enable Next/Last
                     $("#pager-first a").attr('disabled', 'disabled');
                       $("#pager-prev a").attr('disabled', 'disabled');
                      $("#pager-next a").removeAttr('disabled');
                $("#pager-last a").removeAttr('disabled');
              }
else {
                    $(item).hide();
            }
});
     });

__drawPagerButton is a private function that creates that actual button of the pager button. The last argument is an anonymous function which will be the event handler for the click event associated with the button. Notice in the event handler, we determine which page to show (the first page since this is the “go to first” button) and hide all others. This is also where we set the pager record count text to reflect which records are showing. Finally, we disable the buttons to “go to first” and “go to previous” because we are at the first page and cannot go back any further.

To implement this plugin, use the following HTML:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>
    <script src="Scripts/jQuery-tmpl.js" type="text/javascript"></script>
    <script src="Scripts/sjJQuery.js" type="text/javascript"></script>
    <link href="Styles/sjStyles.css" rel="stylesheet" type="text/css" />
    <script language="javascript" type="text/javascript">
        $(document).ready(function () {
            $("#btnBind").click(function () {
                $.ajax({
                    url: "data/dataFile.json",
                    type: "GET",
                    contentType: "application/json; charset=utf-8",
                    beforeSend: function () {
                        // Add any logic to display the loading UI
                    },
                    complete: function () {
                        // Add any logic to hide the loading UI
                    },
                    success: function (jsonData) {
                        var jdata = $.parseJSON(jsonData);
                        var options = {
                            data: jdata,
                            enablePaging: true,
                            pagerContainer: $("#pager-container"),
                            pageSize: 6,
                            template: $("#template")
                        };
                        $("#data-container").DataBinder("bind", options).show("slow");
                    }
,
                    error: function (result) {
                        alert(result.message);
                    }
                });
            });
        });
    </script>
</head>
<body>
    <div>
        <div id="data-container"></div>
        <div style="clear:both;"></div>
        <div id="pager-container"></div>
        <div style="clear:both;"></div>
    </div>
    <div style="margin: 10px 5px;">
        <input type="button" id="btnBind" value="Bind"/>
    </div>
<script id="template" type="text/html">
<ul class='data-item'>
<li class='person-name'>
${LastName}, ${FirstName}
<input type='hidden' id='hdnID${ID}' value='${ID}'/>
</li>
<li>
<label>Email: </label><span class='person-email'>${Email}</span>
</li>
<li>
<a href='javascript:lnkDetails_Click(this, ${ID});'>Details</a>
</li>
</ul>
</script>


</body>
</html>

You can download the $.fn.DataBinder plugin - take the code and run with it! It is your turn to improve the $.fn.DataBinder plugin. Maybe you want to break paging out to be a totally separate plugin or you might add Headers and Footers to the data template. This plugin offers a lot of room for growth and creativity.

Summary

The browser is busting off of the web and becoming a rich application platform. Data binding is a key element to useful applications but is not very simple with traditional client side JavaScript. While it is possible to create a robust data solution using traditional JavaScript, plan on a lot of typing and even more debugging for cross browser support.

jQuery provides a library that simplifies many JavaScript tasks while constantly extending the functionality with its plugin architecture. Using jQuery’s $.tmpl plugin and the $.fn.DataBinder plugin we can create a robust data binding solution with features that allow our users to not just view the data but interact with it.

 

Special Thanks to my technical contributors:

  • Clint Sommer: Application Designer Extraordinaire at MEDEX Global Solutions
  • Tony Ferrara: jQuery Mastermind at MEDEX Global Solutions

 

About the Author

Tim Kulp leads the development team at FrontierMEDEX in Baltimore, Maryland. You can find Tim on his blog or the Twitter feed @seccode, where he talks code, security and the Baltimore foodie scene.

Find Tim on: