Sunday, November 24, 2013

Angular, Web Api 2 & Authentication

This post will take a look at registering an account and logging in with Angular and ASP.NET Web API 2 via cross origin requests (CORS). Authorization with roles and accessing restricted portions of a site will not be covered, just as a heads up. This is to serve more as a starting point.

Some specs regarding the setup:
Visual Studio 2013
Project for the Angular client
Project for Web Api
HotTowel.Angular.Breeze 2.0.1

Alright let's get moving. You can put both projects into one solution or you can put them in separate solutions if you wish. We'll start with the Angular end of things. After you install the HotTowel.Angular.Breeze package, we are good to go. In the app folder, add a folder called register. In the register folder, add a html file called register. The code:
<section id="register-view" class="mainbar" data-ng-controller="register as vm">
   <div class="row-fluid">
      <h4>Create a new account.</h4>
   </div>
   <div class="row-fluid">
      <div>
         <form id="register" class="form-inline">
            <div class="form-group">
               <label class="col-md-2 control-label" for="UserName">User name<label>
               <div class="col-md-10">
                  <input type="text" name="username" placeholder="User Name" data-ng-model="vm.registration.userName" />
               </div>
            </div>
            <div class="form-group">
               <label class="col-md-2 control-label" for="Password">Password<label>
               <div class="col-md-10">
                  <input type="text" name="password" placeholder="Password" data-ng-model="vm.registration.password" />
               </div>
            </div>
            <div class="form-group">
               <label class="col-md-2 control-label" for="ConfirmPassword">Confirm Password<label>
               <div class="col-md-10">
                  <input type="text" name="confirmPassword" placeholder="confirmPassword" data-ng-model="vm.registration.confirmPassword" />
               </div>
            </div>
            <div class="form-group">
               <div class="col-md-offset-2 col-md-10">
                  <input type="submit" class="btn btn-default" value="Register" data-ng-click="vm.sendRegistration()" />
               </div>
            </div>
         </form>
      </div>
   </div>
</section>

Now the code for the register controller, add a script file (register.js) to the register folder:
(function() {
   var controllerId = 'register';

   angular.module('app').controller(controllerId,
      ['$http', register]).config(['$sceDelegateProvider', function ($sceDelegateProvider) {
         $sceDelegateProvider.resourceUrlWhitelist(['self', /^https?:\/\/(cdn\.)?localhost:52635/]);
      }]);

   function register($http) {
      var vm = this;
      var url = "http://localhost:62757/api/Account/Register";

      vm.activate = activate;
      vm.title = 'Register';
      vm.sendRegistration = sendRegistration;
      vm.registration = {
         username = "",
         password = "",
         confirmPassword: ""
      };

      var headers = {
         'Access-Control-Allow-Origin': 'http://localhost:62714/',
         'Access-Control-Allow-Methods': ['POST', 'OPTIONS'],
         'Access-Control-Allow-Headers': 'Content-Type',
         'Access-Control-Allow-Credendtials': 'true',
         'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
      };

      function activate() {
      }
      
      function sendRegistration() {
         var result = "username=" + vm.registration.userName + "&password=" + vm.registration.password + "&confirmPassword=" + vm.registration.confirmPassword;

         $http({
            url: url,
            method: 'POST',
            data: result,
            headers: headers
         }).success(function (data, status, headers, config) {
            // if successful, "" will be returned
         }).error(function (data, status, headers, config) {
            console.log('failed');
         });
      }
   }
})();

A few things to note....The sceDelegateProvider, this allows us a way to configure trusted urls used for client/server communication. Note how a headers object is constructed. It basically creates name/value pairs for the http headers.

Take special note of the Access-Control-Allow-Methods. Here we are configuring POST and OPTIONS. This is because on a POST request, the browser will send a preflight OPTIONS request. We must configure the server in the same fashion or else the POST operation will fail. It is a good idea to inspect the OPTIONS preflight headers and compare them to the headers of the POST request. Try to match them so they are the same. If you run into problems in this area, make sure that the headers for the POST and OPTIONS headers are the same and that the server is expecting those headers as well.

Lastly, if they call succeeds, you will see an empty string returned if you inspect the result in Chrome.

In the app folder, add a folder called login. Next add a html file called login. The code:
<section id="login-view" class="mainbar" data-ng-controller="login as vm">
   <div class="row-fluid">
      <h4>Log in.</h4>
   </div>
   <div class="row-fluid">
      <div>
         <form id="register" class="form-inline">
         <form id="register" class="form-inline">
            <div class="form-group">
               <label class="col-md-2 control-label" for="UserName">User name<label>
               <div class="col-md-10">
                  <input type="text" name="userName" placeholder="User Name" data-ng-model="vm.logIn.userName" />
               </div>
            </div>
            <div class="form-group">
               <label class="col-md-2 control-label" for="Password">Password<label>
               <div class="col-md-10">
                  <input type="text" name="password" placeholder="Password" data-ng-model="vm.logIn.password" />
               </div>
            </div>
            <div class="form-group">
               <div class="col-md-offset-2 col-md-10">
                  <input type="submit" class="btn btn-default" value="Log In" data-ng-click="vm.sendLogIn()" />
               </div>
            </div>
         </form>
      </div>
   </div>
</section>

Add a js file to the login folder called login.js. The code for the controller:
(function () {
   var controllerId = 'login';

   angular.module('app').controller(controllerId,
      ['$http', login]).config(['$sceDelegateProvider', function ($sceDelegateProvider) {
         $sceDelegateProvider.resourceUrlWhitelist(['self', /^https?:\/\/(cdn\.)?localhost:52635/]);
      }]);

   function login($http) {
      var vm = this;
      var accessToken = "";
      var url = "http://localhost:62757/Token";

      vm.activate = activate;
      vm.title = 'Log In';
      vm.sendLogIn = sendLogIn;
      vm.logIn = {
         username = "",
         password: ""
      }

      var headers = {
         'Access-Control-Allow-Origin': 'http://localhost:62714/',
         'Access-Control-Allow-Methods': ['POST', 'OPTIONS'],
         'Access-Control-Allow-Headers': 'Content-Type',
         'Access-Control-Allow-Credentials': 'true',
         'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
      };

      function activate() {
      }

      function sendLogIn() {
         var result = "grant_type=password&" + "username=" + vm.logIn.userName + "&password=" + vm.logIn.password;

         $http({
            url: url,
            method: 'POST',
            data: result,
            headers: headers
         }).success(function (data, status, headers, config) {
            accessToken = data.access_token;
         }).error(function (data, status, headers, config) {
            console.log('failed');
         });
      }
   }
})();
The login setup is quite similar to register. Again, $sceDelegateProvider.resourceUrlWhiteList provides safe url communication from client to server. The headers are configured in the same fashion as noted above.

When the request is successful, the Token endpoint returns a token. This is then stored in the accessToken variable and can now be used for any request that requires authorization for a resource. More about the mysterious Token endpoint below.

There really isn't much to do configure Web Api to accept CORS requests. We will need to install two packages via Nuget. Those packages are:

Microsoft.AspNet.WebApi.Cors
Microsoft.Owin.Cors

The first change to make is in Startup.Auth.cs. In this file, there is a method called ConfigureAuth. We need to add one line of code, I put it at the top:

public partial class Startup
{
   // other members

   public void ConfigureAuth(IAppBuilder app)
   {
      app.UseCors(CorsOptions.AllowAll);
   }
}

There is also something else in this file that is a key piece of information. In the Startup method, there is some code for the creation and initialization of a class called OAuthAuthorizationServerOptions. You will see a property called TokenEndpointPath = new PathString("/Token"). Ok so what is it?

With every iteration of the framework, Microsoft like to change membership. If you open the Web Api AccountController, you will see a method for registering, the Register method. This has been around for awhile in ASP.NET MVC. Then you would expect that there would be a method for log in, that's the way it was before. Well, if you look in the AccountController, you will not find a Login method at all. What the heck happened?

It took a little digging. Recall the OAuthAuthorizationServerOptions from above and the TokenEndpointPath. In the login controller we also configured the url for /Token. The actual login now takes place in OWIN middleware. You route the request to the Token endpoint and then the request goes off into the ether.

We have one change left to make on the server. In the AccountController, locate the Register method. We need to decorate this method to allow CORS requests:
[AllowAnonymous]
[Route("Register")]
[EnableCors(origins: "http://localhost:62714", headers: "accept, content-type, origin", methods: "POST, OPTIONS")]
public async Task Register(RegisterBindingModel model)
{
   // registration logic
}
We have to ensure that the client and server and configured to expect the same headers. It was mentioned above that when configuring a POST operation, the preflight OPTIONS request has to be configured as well. Be very aware and careful about this as it may cause you more pain (and burning many hours) than you would want.

Wednesday, November 13, 2013

JavaScript: Working with Arrays

I was going to include this material with the last post on jQuery but this material really deserves it's own attention. Many of us .NET developers are hesitant about JavaScript, and for good reason, it's a pain in the ass. But, as with all things, it is a mater of practice, practice, practice. This post will present working with JavaScript arrays and some of the slight differences between them. I'm running this in Visual Studio 2012 with Chrome.

To get rolling, we will declare some arrays to hold the data, some variables, and pass the variables to a function. The function will create a key that will be looked up in the cache and add if the key is not present.
var productCache = [];
var productCache2 = [];

var value1 = 1;
var value2 = 2;
var value3 = 3;

addToCache(value1);
addToCache(value2);
addToCache(value3);

function addToCache(val) {
   var id = "productId";
   var value = val;
   var key = id + "-" + value;

   productCache[key] = value;
   productCache2.push(value);
}
As displayed above, there is two ways to shuttle data into an array. You can use an index, or use the push function. Now, let's call the length property on each array and see what we have, afterall, using the length property is a pretty common scenario.
console.log(productCache.length);
console.log(productCache2.length);

Result: 0
Result: 3
How about that. Can that really be? Let's take a look at the objects with the developer tools.

 
If you read the post on jQuery tips you would know why this is happening. Indexes, by default, are numeric. In the first cache, we are using strings for indexes, this is why we are seeing nothing when length is called and when we peek into the object. Rest assured, the data is in there, that is what we will look at next.
Now we will look at iterating over these arrays, how to get the keys, how to get the values. You can accomplish this several different ways. This part may be boring but as developers, we like our options don't we? We will iterate the following ways: plain for loop, hasOwnProperty, and using ECMA 5. Both of the arrays will produce the same value and the technique is the same.
// Get keys with plain for loop
for (var key in productCache) {
   console.log("logging key: " + key);
}

Result:
logging key: productId-1
logging key: productId-2
logging key: productId-3

// Get keys using hasOwnProperty
// hasOwnProperty will inspect properties that have been added, 'direct properties'
// it will not inspect properties inherited from Object through the prototype chain
for (var key in productCache) {
   if (productCache.hasOwnProperty(key)) {
      console.log("logging key: " + key);
   }
}

Result:
logging key: productId-1
logging key: productId-2
logging key: productId-3

// Get keys using ECMA 5
var keys = Object.keys(productCache).map(function(key) {
   console.log("logging key: " + key;
   return key;
});

console.log(keys);

Result:
logging key: productId-1
logging key: productId-2
logging key: productId-3

["productId-1", "productId-2", "productId-3"]

// Get values with plain for loop
for (var key in productCache) {
   var value = productCache[key];
   console.log("logging value: " + value);
}

Result:
logging value: 1
logging value: 2
logging value: 3

// Get values using hasOwnProperty
// hasOwnProperty will inspect properties that have been added, 'direct properties'
// it will not inspect properties inherited from Object through the prototype chain
for (var key in productCache) {
   if (productCache.hasOwnProperty(key)) {
      var value = productCache[key];
      console.log("logging value: " + value;
   }
}

Result:
logging value: 1
logging value: 2
logging value: 3

// Get values using ECMA 5
var values = Object.keys(productCache).map(function(key) {
   console.log("logging value: " + productCache[key]);
   return productCache[key];
});

console.log(values)

Result:
logging value: 1
logging value: 2
logging value: 3

[1, 2, 3]
Deleting from an array, you have to be careful when you are doing this. Depending on how you are storing items in the array and which way you choose to delete an item, can have some unexpected side effects. Just to recap and try to readjust our brains a little bit, we have two arrays, and this is what they look like:

productCache:
[productId-1] = 1
[productId-2] = 2
[productId-3] = 3

productCache2:
0: 1
1: 2
2: 3

Let's start deleting stuff and see what goes boom. To delete an item, you use the, well, delete keyword.
for (var index in productCache) {
   var item = productCache[index];
   if (item == "3") {
      delete productCache[index];
   }
}
Before showing the results, I'd like to call attention to something. Typically, when we deal with indexes we think of them as a number when we are iterating. But when you step outside of a language that possess the awesomeness of C#, you can't be guaranteed anything. Anyway, recall that in this array, we are storing key/value pairs and in our case, the key is a string, not a number. Above, when we are iterating through this array, the first time through, index is "productId-1", second time through, "productId-2", third time, "productId-3". Let's have a look at the results....

 
Cool, it worked, no surprises. Let's move on to the other array. Since we just pushed values into this array, it will have the normal indexes of 0, 1, and 2.
for (var index in productCache2) {
   var item = productCache2[index];
   if (item == "3") {
      delete productCache2[index];
   }
}
 
 
 
Uh oh indeed. See what happened there? The item did in fact get removed but the length did not change! Is there something wrong with our eyes??? Let's investigate a bit more...

 
Yes, that is the expected behavior when using delete with arrays. That "undefined x 1" will be put in the array for any item you delete. The alternative, is to use the splice method. The first parameter that splice takes is the index, the second parameter is a number indicating how many items to cleave.
for (var index in productCache2) {
   var item = productCache2[index];
   if (item == "3") {
      productCache2.splice(index, 1);
   }
}
 
 
 
 
Splice with not work with our other array. This is rather simple, the first parameter to splice expects a number. That array uses strings for keys.

Saturday, October 26, 2013

jQuery Tips

In this post we will be examining some jQuery functions as well as selectors and some JavaScript tips that may help you along the way in the wild wild west of JavaScript.

We will be looking at the following:
The input/:input selector.
$.inArray()
The parent/child selector >.
Find.
Id starts with selector.
Combining input and id starts with.
Ancestor Descendant / Parent Child selector.
Checkboxes.
Radio Buttons.

For our first example, we will start with this snippet of html:
<div id="container">
   <table class="theTable">
      <tr>
         <td>
            <input type="text" id="productId" name="productId" value="5" />
         </td>
      </tr>
   <table>
</div>

We will look at several different ways of getting the value of the textbox and storing the value in an array. First, input and :input.
var productIds = [];

$('.theTable input').each(function () {
   var id = $(this).prop('id');
   if (id == "productId") {
      var isInArray = $.inArray($(this).prop('value'),productIds);
      if (isInArray == -1) {
         productIds.push($(this).attr('value'));
      }
   }
});

$('.theTable :input').each(function () {
   var id = $(this).prop('id');
   if (id == "productId") {
      var isInArray = $.inArray($(this).prop('value'), productIds);
      if (isInArray == -1) {
         productIds.push($(this).attr('value'));
      }
   }
});

The input selector will only select items designated with <input /> tags. The :input selector will match inputs, textareas, select items, and buttons. However, in order to get a checkbox, you use :checkbox or [type="checkbox"].

The $.inArray() function takes as arguments a value and an array. If the value is not found in the array, -1 is returned, if the value is found, the index of that item is returned.
And now a gotcha....
$('.theTable > input').each(function () {
   var id = $(this).prop('id');
   if (id == "productId") {
      var isInArray = $.inArray($(this).prop('value'), productIds);
      if (isInArray == -1) {
         productIds.push($(this).attr('value'));
      }
   }
});

Note the subtle change in the selector, the addition of the greater than symbol. The greater than symbol is a child selector, it will only select the direct children of an element. Given the html above, nothing will be found as the table element does not have any direct children that are inputs. More further down the post.

Our last example in this scenario is using the find() function. Find takes a selector, element, or an object and searches for descendants (down the DOM, only one level). Here we are passing in a selector for inputs.
$('.theTable').find('input').each(function () {
   var id = $(this).prop('id');
   if (id == "productId") {
      var isInArray = $.inArray($(this).prop('value'), productIds);
      if (isInArray == -1) {
         productIds.push($(this).attr('value'));
      }
   }
});

The id starts with selector is very helpful. It's great when id's are built dynamically and you need to rip through many inputs to get the value.
$('[id^="product"]').each(function () {
   productIds.push($(this).val());
});

Of course you can combine selectors for more precision.
$('input:text[id^="product"]').change(function () {
   productIds.push($(this).val());
});

There is a gotcha above when checking if the key is in the array. When debugging, you may peek into the object and see values but you call length on the array, the length will be....0. This is because the length property only tracks properties that have numeric indexes. In our example above, we are using strings.

Alright let's go more in depth with ancestor/descendant and parent/child selectors. Honestly, I find ancestor/descendant more useful. For the follow examples, we will be using this snippet of html:
<div id="container">
   <table id="tbl" class="theTable">
      <tr>
         <td>
            <input type="text" id="productId-1" name="productId-1" value="5" />
         </td>
         <td>
            <input type="text" id="quantity-1" name="quantity-1" value="10" />
         </td>
         <td>
            <input type="text" id="shipDate-1" name="shipDate-1" value="Now" />
         </td>
      </tr>
      <tr>
         <td>
            <input type="text" id="productId-2" name="productId-2" value="10" />
         </td>
         <td>
            <input type="text" id="quantity-2" name="quantity-2" value="15" />
         </td>
         <td>
            <input type="text" id="shipDate-2" name="shipDate-2" value="Later" />
         </td>
      </tr>
   </table>
</div>
What you really have to respect and like about jQuery is the flexibility that it gives. The following examples below are several ways of DOM manipulation and I am sure there are many more.
// this works as long as product id is the first td in each tr
// and remember, find only travels down one level
var tableRows = $('.theTable tr');
$.each(tableRows, function() {
   console.log("selector: $('.theTable tr') " + $(this).find('input').prop('id'));
});

Result:
selector: $('.theTable tr') productId-1
selector: $('.theTable tr') productId-2

// this gets all the td's directly
var tds = $('.theTable tr td');
$.each(tds, function() {
   console.log("selector: $('.theTable tr td') " + $(this).find('input').prop('id'));
});

Result:
selector: $('.theTable tr td') productId-1
selector: $('.theTable tr td') quantity-1
selector: $('.theTable tr td') shipDate-1
selector: $('.theTable tr td') productId-2
selector: $('.theTable tr td') quantity-2
selector: $('.theTable tr td') shipDate-2

// this will get all td's for each tr
var tdsEachTr = $('.theTable tr').children();
$.each(tdsEachTr, function() {
   console.log("selector: $('.theTable tr').children() " + $(this).find('input').prop('id'));
});

Result:
selector: $('.theTable tr').children() productId-1
selector: $('.theTable tr').children() quantity-1
selector: $('.theTable tr').children() shipDate-1
selector: $('.theTable tr').children() productId-2
selector: $('.theTable tr').children() quantity-2
selector: $('.theTable tr').children() shipDate-2

// this will get all td's for each tr as well
var tdEachTr2 = $('tr').children();
$.each(tdEachTr2, function() {
   console.log("selector: $('tr').children() " + $(this).find('input').prop('id'));
});

Result:
selector: $('tr').children() productId-1 
selector: $('tr').children() quantity-1 
selector: $('tr').children() shipDate-1
selector: $('tr').children() productId-2 
selector: $('tr').children() quantity-2
selector: $('tr').children() shipDate-2

// get all td's directly
var tdsDirect = $('td > td');
$.each(tdsDirect, function() {
   console.log("selector: $('tr > td') " + $(this).find('input').prop('id'));
});

Result:
selector: $('tr > td') productId-1
selector: $('tr > td') quantity-1
selector: $('tr > td') shipDate-1
selector: $('tr > td') productId-2
selector: $('tr > td') quantity-2
selector: $('tr > td') shipDate-2
For the final segment, we will be working with checkboxes and radio buttons. Checkboxes and radio buttons are similar so the code is pretty much interchangeable. The following snippet of html will be used to illustrate:
<div id="checkboxes">
   <input type="checkbox" id="ckBox1" name="ckBox1" value="First Check" /> First Check
   <input type="checkbox" id="ckBox2" name="ckBox2" value="Second Check" /> Second Check
   <input type="checkbox" id="ckBox3" name="ckBox3" value="Third Check" /> Third Check
   <input type="checkbox" id="ckBox4" name="ckBox4" value="Fourth Check" /> Fourth Check
</div>
<div id="radiobuttons">
   <input type="radio" id="rdo1" name="rdo1" class="rdo" value="First Radio" />First Radio
   <input type="radio" id="rdo2" name="rdo2" class="rdo" value="Second Radio" />Second Radio
   <input type="radio" id="rdo3" name="rdo3" class="rdo" value="Third Radio" />Third Radio
   <input type="radio" id="rdo4" name="rdo4" class="rdo" value="Fourth Radio" />Fourth Radio
</div>
<div>
   <input type="submit" id="btnSubmit" name="btnSubmit" value="Submit" />
</div>

// get all checkboxes
var checkboxes = $('#checkboxes input:checkbox');
$.each(checkboxes, function() {
   console.log("selector: $('#checkboxes input:checkbox')");
   console.log("checkbox id: " + $(this).prop('id'));
   console.log("checkbox value: " + $(this).val());
});

Result:
selector: $('#checkboxes input:checkbox') 
checkbox id: ckBox1 
checkbox value: First Check

selector: $('#checkboxes input:checkbox') 
checkbox id: ckBox2
checkbox value: Second Check

selector: $('#checkboxes input:checkbox') 
checkbox id: ckBox3 
checkbox value: Third Check

selector: $('#checkboxes input:checkbox') 
checkbox id: ckBox4 
checkbox value: Fourth Check

// inspect checkbox on change
$('[id^="ckBox"]').change(function() {
   var isChecked = $(this).is(':checked');
   if (isChecked == true) {
      console.log("checkbox id: " + $(this).prop('id'));
      console.log("checkbox value: " + $(this).val());
   }
});

// get all checked checkboxes on submit
$('#btnSubmit').click(function(e) {
   e.preventDefault();
   $('#checkboxes input:checkbox:checked').each(function() {
      console.log("selector: $('#checkboxes input:checkbox:checked')");
      console.log("checkbox id: " + $(this).prop('id'));
      console.log("checkbox value: " + $(this).val());
   });

   // or
   $('#checkboxes').children('input:checked').each(function() {

   });

   // or
   $('#checkboxes input[type=checkbox]:checked').each(function() {

   });
});

// get all radio buttons
var radiobuttons = $('#radiobuttons input:radio');
$.each(radiobuttons, function() {
   console.log("selector: $('#radiobuttons input:radio')");
   console.log("radio id: " + $(this).prop('id'));
   console.log("radio value: " + $(this).val());
});

Result:
selector: $('#radiobuttons input:radio') 
radio id: rdo1
radio value: First Radio

selector: $('#radiobuttons input:radio') 
radio id: rdo2
radio value: Second Radio

selector: $('#radiobuttons input:radio') 
radio id: rdo3
radio value: Third Radio

selector: $('#radiobuttons input:radio') 
radio id: rdo4
radio value: Fourth Radio

// inspect radio button on change
$('[id^="rdo"]').change(function() {
   var result = $(this).is(':checked');
   if (result == true) {
      console.log("radio id: " + $(this).prop('id'));
      console.log("radio value: " + $(this).val());
   }
});

// get all selected radio buttons on submit
$('#btnSubmit').click(function(e) {
   e.preventDefault();
   $('#radiobuttons input:radio:checked').each(function() {
      console.log("selector: $('#radiobuttons input:radio:checked')");
      console.log("radio id: " + $(this).prop('id'));
      console.log("radio value: " + $(this).val());
   });

   // or
   $('#radiobuttons').children('input:checked').each(function() {

   });

   // or
   $('#radiobuttons input[type=radio]:checked').each(function() {

   });
});

Sunday, September 29, 2013

Handlebars & jQuery UI Accordion (Sort Of) Part 2

We will be picking up from where we left off from the most recent post. Our friends in "the business" have come up with a new request since we went out for celebratory pizza. This is what they want now....

"We really like what we see but...there could be a lot of items on the screen. It would be really nice if a user entered a value in a text, and the text box stayed open. This would be a good indication to the user that they made a selection...a visual cue that they performed work."

Can we do this? Yes we can. The problem with this request is that now they are asking for a behavior that was not an intended behavior for the jQuery UI accordion. At times, we are at the mercy of the business and must give them what they want. This post will demonstrate how we can do this. Nevertheless, this is a very good exercise of creating a pseudo-accordion. It looks like a jQuery UI accordion, acts like a jQuery UI accordion, but there is no actual call to the jQuery accordion.

This example will be a little different from the previous as I will be showcasing a different screen mock up. Nevertheless, it's not much of a stretch. Let's dive in an start with our model class:
public class Item
{
   public string ItemId { get; set; }
   public string ItemDescription { get; set; }
   public string PackQuantity { get; set; }
   public string PackSize { get; set; }
}

Let's write the controller:
public class PanelController : Controller
{
   public ActionResult Index()
   {
      return View();
   }

   [HttpGet]
   public object GetItemData()
   {
      List<Item> items = new List<Item>()
      {
         new Item() { ItemId = "1", ItemDescription = "Item A", PackQuantity = "", PackSize = "5" },
         new Item() { ItemId = "2", ItemDescription = "Item B", PackQuantity = "", PackSize = "10" },
         new Item() { ItemId = "3", ItemDescription = "Item C", PackQuantity = "", PackSize = "15" }
      };

      var data = items.GroupBy(x => x.ItemDescription)
                      .Select(y => new { Headers = y.Key, Contents = y.Select(z => z).ToList() });
      var json = JsonConvert.SerializeObject(data);
      return json;
   }
}
The interesting thing to note is the return type on the GetItemData method. Usually we would write return Json(data, JsonRequestBehavior.AllowGet);, above is just showing a different approach. Let's now write the view for the Index method. We will be doing this in steps, first the template, followed by the css, finally, the JavaScript.
@{
   ViewBag.Title = "Panel";
}
<br />
<div id="tmplTarget"></div>

<script id="itemsTmpl" type="text/x-handlebars-template">
   <div id="itemsContent">
      {{#each this}}
         <div>
            <h3>
               <span class="ui-icon ui-icon-plus" style="display: inline-block;" />
               <a href="#">{{this.Headers}}</a>
            </h3>
            <div class="tblDiv">
               <table class="itemsContentTbl">
                  <tr>
                     <th class="itemsContentHeader"></th>
                     <th></th>
                     <th>Pack Qty</th>
                     <th>Pack Size</th>
                  </tr>
                  {{#each this.Contents}}
                  <tr>
                     <td class="itemDescription">{{this.ItemDescription}}</td>
                     <td class="tdCkItemDesc">
                        <input type="checkbox" id="ck-{{this.ItemDescription}}"
                                  name="ck-{{this.ItemDescription}}" />
                     </td>
                     <td class="packQty" style="text-align: center">
                        <input type="text" id="txtPackQuantity-oi-{{this.ItemDescription}}
                                  name="txtPackQuantity-oi-{{this.ItemDescription}} />
                     </td>
                     <td class="packSize">{{this.PackSize}}</td>
                     <input type="hidden" id="hdnPackSize-oi-{{this.ItemDescription}}-ps-{{this.PackSize}}"
                               name="hdnPackSize-oi-{{this.ItemDescription}}-ps-{{this.PackSize}}"
                               value="{{this.PackSize}}" />
                  </tr>
                  {{/each}}
               </table>
            </div>
         </div>
      {{/each}}
   </div>
</script>

Not all of the classes will be styled, some are just used as hooks for jQuery. However, note the span tag in the template. We need all of the headers to have the plus icon when the page loads. Since we are not using the accordion we need to leverage the jQuery UI css. The ui-icon and ui-icon-plus classes will allow us to achieve this. Also, in order for the icon to be displayed correctly, the span element must be rendered as an inline-block. It is quite common when using the accordion to see the icons smashed into the text, inline-block is the solution. And again, just inlining the css for illustrative purposes.
Let's take care of the AJAX call and the template so we can see what we are working with.
<script src="~/Scripts/jquery-1.8.2.js"></script>
<script src="~/Scripts/jquery-ui-1.8.24.js"></script>
<script src="~/Scripts/handlebars.js"></script>
<script type="text/javascript">
   $(function() {
      $.ajax({
         url: '@Url.Action("GetItemData")',
         contentType: 'application/json; charset=utf-8',
         dataType: 'json',
         cache: false,
         success: function(data) {
            var source = $('#itemsTmpl').html();
            var template = Handlebars.compile(source);
            $('#tmplTarget').html(template(data));
         },
         error: function(jqXHR, textStatus, errorThrown) {
            alert(errorThrown);
         }
      });
   });
</script>

 
We'll come back to the jQuery code later. For now, let's take care of some styling.
.itemsContentTbl {
   border-collapse: collapse;
   /* center table */
   margin: 10px auto 10px auto
}

.itemsContentTbl th {
   background: #002a5f;
   color: white;
   padding-left: 20px;
   padding-right: 20px;
   border: 1px solid black;
   text-align: center;
}

.itemsContentTbl td {
   border: 1px solid black;
   text-align: center;
   vertical-align: middle;
}
Let's have a look....

 
Ok we have two issues we need to clean up. The checkbox has a border around it, which was introduced by the css class itemsContentTbl td. Also, we should center the textbox.
In the Handlebars template, we just need to add classes to the checkbox and textbox inputs. Add the classes ckItemDesc for the checkbox and txtPackQty for the textbox. The css for those classes:
.ckItemDesc {
   border: none !important;
   padding-left: 30px;
}

.txtPackQty {
   width: auto;
   margin-left: 60px;
   margin-right: 30px;
}


 
Everything else will be done with jQuery. All of the following code should be placed in the AJAX success callback. We do not want to display the item descriptions in the html table so we have to hide those.
$('.itemsContentHeader, .itemDescription').hide();

 
Style the headers for our panel/accordion.
$('h3').addClass('ui-accordion-header ui-helper-reset ui-state-default ui-corner-all');

 
Style the divs below the header.
$('.tblDiv').addClass('ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom');

 
The divs that we just styled have to be hidden upon page load so the accordion/panel appears to be closed.
$('.tblDiv').hide();
 
Now we need to take care of the checkbox. When a user enters a value into the textbox, we would like that to automatically check the check box. If a checkbox is clicked, we will use that as an indication to keep that panel open, essentially freezing it. You may say, well, if a user manually checks the checkbox and does not enter a value into the textbox, it will still freeze the panel right? Correct. This may not be ideal but a user could check it as a reminder to come back and decide if they want to enter a value into the checkbox. The checkbox is optional, things will work just as well without it. Anyway, the code:
$('input:text[id^="txtPackQuantity"]').change(function () {
   $(this).parent().prev().children().attr('checked', 'checked');
});

Right now the panel control is not responding to mouse clicks. The next part is where all the heavy lifting comes in. The toggle function in jQuery is exactly what we need to make this work. The toggle function is passed in two functions, one for the first mouse click, and the second function for the second mouse click. As we have been doing, we will be harnessing jQuery UI css classes to make all the magic happen. Since the span element in the template has the icon, that span will be the target for the toggling.
$('span').toggle(
   function() {
      $(this).addClass('ui-icon-minus');
      $(this).parent().next().removeClass('ui-accordion-header ui-helper-reset ui-state-default ui-corner-all').addClass('ui-accordion-header ui-helper-reset ui-state-default ui-state-active ui-corner-top');
      $(this).parent().next().show('slow');
   },
   function() {
      var ckBox = $(this).parent().parent().find('.ckItemDesc');
      if ($(ckBox).is(':checked')) {
         $(this).removeClass('ui-icon-plus');
         $(this).addClass('ui-icon ui-icon-minus');
         return;
      } else {
         $(this).removeClass('ui-icon-minus');
         $(this).addClass('ui-icon-plus');
         $(this).parent().next().removeClass('ui-accordion-header ui-helper-reset ui-state-default ui-state-active ui-corner-top').addClass('ui-accordion-header ui-helper-reset ui-state-default ui-corner-all');
         $(this).parent().next().hide('slow');
      }
   }
);

The first time the plus icon is clicked, we add the minus icon. Then we remove the css classes that are used when a panel is not displayed and add the css classes for when a panel is displayed/active. Finally, since that div is hidden, we just show it.

On the second click, if the checkbox is checked, we need to freeze the panel. To do that, it's just simply a matter of removing the plus icon css class and adding the minus icon css class. Using the return keyword will break out of the code.

If the checkbox is not clicked, we need to remove the minus icon css class, add the plus icon css class, remove the css classes indicating the panel is active, add the css classes indicating the panel is not active, and finally, hide that div.

Now we should be able to have multiple panels open, freeze panels, and expand collapse panels where the text box does not have any data.
 

Below is all the jQuery code together.
<link href="~/Content/themes/base/jquery.ui.all.css" rel="stylesheet" />
<script src="~/Scripts/jquery-1.8.2.js"></script>
<script src="~/Scripts/jquery-ui-1.8.24.js"></script>
<script src="~/Scripts/handlebars.js"></script>
<script type="text/javascript">
   $(function() {
      $.ajax({
         url: '@Url.Action("GetItemData")',
         contentType: 'application/json; charset=utf-8',
         dataType: 'json',
         cache: false,
         success: function(data) {
            var source = $('#itemsTmpl').html();
            var template = Handlebars.compile(source);
            $('#tmplTarget').html(template(data));

            // do not want to show content headers and descriptions
            $('.itemsContentHeader, .itemDescription').hide();

            // style the headers
            $('h3').addClass('ui-accordion-header ui-helper-reset ui-state-default ui-corner-all');

            // style divs below header
            $('.tblDiv').addClass('ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom');

            // close the divs
            $('.tblDiv').hide();

            // checking checkbox will be cue to freeze panel, check when field changes
            $('input:text[id^="txtPackQuantity"]').change(function() {
               $(this).parent().prev().children().attr('checked', 'checked');
            });

            // toggle plus minus
            $('span').toggle(
               function() {
                  $(this).addClass('ui-icon-minus');
                  $(this).parent().next().removeClass('ui-accordion-header ui-helper-reset u-state-default ui-corner-all').addClass('ui-accordion-header ui-helper-reset ui-state-default ui-state-active ui-corner-top');
                  $(this).parent().next().show('slow');
               },
               function() {
                 var ckBox = $(this).parent().parent().find('.ckItemDesc');
                 if ($(ckBox).is(':checked')) {
                   $(this).removeClass('ui-icon-plus');
                   $(this).addClass('ui-icon ui-icon-minus');
                   return;
                 } else {
                    $(this).removeClass('ui-icon-minus');
                    $(this).addClass('ui-icon-plus');
                    $(this).parent().next().removeClass('ui-accordion-header ui-helper-reset ui-state-default ui-state-active ui-corner-top').addClass('ui-accordion-header ui-helper-reset ui-state-default ui-corner-all');
                    $(this).parent().next().hide('slow');
                 }
               }
            );
         },
         error: function(jqXHR, textStatus, errorThrown) {
            alert(errorThrown);
         }
      });
   });
</script>

Monday, September 23, 2013

Handlebars & jQuery UI Accordion Part 1

This post is modeled after something I have recently done at work. Here is the rough description....

The application is an ASP.NET Web Forms application that is at least five years old. The DevExpress tool suite is used heavily throughout the site. The business wanted to make a presentation change of a grid, thing is, the DevExpress grid could not do it. I was tasked with handling this so I went with Handlebars.js and the Accordion control from jQuery UI. It was necessary to use templating as the data was dynamic. This ended up being a very nasty task due to various button click handlers and the nasty postback behavior of ASP.NET.

For this walkthrough, I'm going with ASP.NET MVC just to simplify things. Make sure to add Handlebars through NuGet to the solution. Let's start with the models we will be using.....
public class Material
{
   public string MaterialDescription { get; set; }
   public int RequestQty { get; set; }
   public int PackQty { get; set; }
   public int PackSize { get; set; }
   public int TotalApproved { get; set; }
   public int AvailableInv { get; set; }
}

I'm adding a view model to send to the client because generally, I do not like returning anonymous objects. When I can, I always like to be explicit and declare the type. The view model:
public class IndexViewModel
{
   public string Headers { get; set; }
   public IEnumerable<Material> Contents { get; set; }
}

The Index method of whatever controller you are using can be left as is. Add the view. The view will contain the target div for the accordion as well as the Handlebars template. The view:
@model IEnumerable<YourSolution.Models.IndexViewModel>
@{
   ViewBag.Title = "Handlebars Accordion";
}


<div id="target"></div>

<script id="testTmpl" type="text/x-handlebars-template">
   <div id="test">
      {{#each this}}
         <h3><a href="#">{{this.Headers}}</a><h3>
         <div>
            <table class="table">
               <tr>
                  <th>Request Qty</th>
                  <th>Pack Qty</th>
                  <th>Pack Size</th>
                  <th>Total Approved</th>
                  <th>Available Inv</th>
               </tr>
               {{#each this.Contents}}
                  <tr>
                     <td>{{this.RequestQty}}</td>
                     <td>{{this.PackQty}}</td>
                     <td>{{this.PackSize}}</td>
                     <td>{{this.TotalApproved}}</td>
                     <td>{{this.AvailableIn}}</td>
                  </tr>
               {{/each}}
            </table>
         </div>
      {{/each}}
   </div>
</script>

Handlebars is pretty slick and easy to use. To loop through a collection is fairly straightforward, just use #each and this, where this of course refers to the current item. This also shows a nested data structure and how to perform nested looping. We have two properties in the view model, Headers and contents. In the first each, we are just writing out the Headers property. To access any properties you just use dot syntax. When you want to access the nested object, just embed another each, and you will have access to the nested property/object.
Let's move on to the JavaScript. For simplicity, I am putting it in the view as well.
<script type="text/javascript">
   $(function() {
      $.ajax({
         url: '/AccordionTmpl/GetData',
         type: 'GET',
         contentType: 'application/json; charset=utf-8',
         dataType: 'json',
         success: function(data) {
            var source = $('#testTmpl')html();
            var template = Handlebars.compile(source);
            $('#target').html(template(data));

            $('#test').accordion({
               active: false,
               header: 'h3',
               collapsible: true,
               icons: {
                  "header": "ui-icon-plus",
                  "headerSelected": "ui-icon-minus"
               }
            });
         },
         error: function(response) {
            alert(response.responseText);
         }
      });
   });
</script>

If you've done any templating this should look familiar. Grab the html of the template, compile it, feed the data into the template, take the html and add it to the target. The accordion configuration is pretty basic as well.
There is one css class in our template. We will add some styling to the table, headers and rows.
.tbl {
   border-collapse: collapse;
   /* center the table */
   margin: 10px auto 10px auto;
}

.tbl th {
   background: #002a5f;
   color: white;
   padding-left: 20px;
   padding-right: 20px;
   border: 1px solid black;
}

.tbl td {
   border: 1px solid black;
   text-align: center;
}
We have to go back to the server and write the method to return data:
[HttpGet]
public ActionResult GetData()
{
   List<Material> materials = new List<Material>()
   {
      new Material(){MaterialDescription = "Big Bang Theory Sheldon Shirt", RequestQty = 3, PackQty = 3, PackSize = 3, TotalApproved = 3, AvailableInv = 6},
      new Material(){MaterialDescription = "Big Bang Theory Leonard Shirt", RequestQty = 2, PackQty = 2, PackSize = 2, TotalApproved = 2, AvailableInv = 7},
      new Material(){MaterialDescription = "Big Bang Theory Penny Shirt", RequestQty = 1, PackQty = 1, PackSize = 1, TotalApproved = 1, AvailableInv = 9},
      new Material(){MaterialDescription = "Big Bang Theory Kripke Shirt", RequestQty = 1, PackQty = 1, PackSize = 1, TotalApproved = 1, AvailableInv = 10},
   };

   IEnumerable<IndexViewModel> data = materials
                                            .GroupBy(x => x.MaterialDescription)
                                            .Select(y => new IndexViewModel()
                                            {
                                               Headers = y.Key, 
                                               Contents = y.Select(z => z).ToList()
                                            });

   return Json(data, JsonRequestBehavior.AllowGet);
}

Let's run and see what we got...
 

Not too shabby. We did some good work, should probably reward ourselves with some pizza.

*One hour later*

It seems our friends in the business saw what we did and they have a request. Since the list of items can be quite a few, they would like it if more than one panel could stay open so a user can remember where they were.

This can be done even though the accordion was not designed to do this. The solution, initially, seems very simple. The answer lies in the structure of the accordion. You have to change the target of the accordion. If you look at the documentation for the accordion, the structure of your html has to look like this:

<div id="accordion">
   <h3>First Header</h3>
   <div>First Content Panel</div>
   <h3>Second Header</h3>
   <div>Second Content Panel</div>
</div>

Our structure is different but still worked. We are going to change the target for the accordion from $('#test) to $('#test > div'). Our target is now the child div of the test div. The only other change we need to make is to add some css to the anchor tag, right after the accordion declaration. Make the changes:
$('#test > div').accordion({
   active: false,
   header: 'h3',
   collapsible: true,
   icons: {
      "header": "ui-icon-plus",
      "headerSelected": "ui-icon-minus"
   }
});

$('a').css({ "display": "inline-block" });

Now to run...
 
Looks good! The css that was added to the anchor tag is very, very important. If you are ever using the accordion and your icons are mashed in with the text, applying that css to the anchor will solve it. Another solution is the following:
<h3>
   <span style="display: inline-block;" />
   <a href="#">{{this.Headers}}</a>
</h3>

Really the only difference is to what element you apply the css. Again, just inlining the css for illustrative purposes.

In the next post, we will revisit this scenario as our friends from the business have another request.

Monday, July 15, 2013

Fly Out Menu

Navigation can grow quickly. We have become quite accustomed to dropdown lists and fly out menus for some time. Many solutions can be quite complicated and cumbersome. This post will examine building a fly out navigation menu....with no jQuery.

First step, laying out the HTML structure:
<nav>
   <ul class="top-nav">
      <li><a href="#">Home</a></li>
      <li><a href="#">About</a>
         <ul class="sub-nav">
            <li><a href="#">First</a></li>
            <li><a href="#">Second</a></li>
            <li><a href="#">Third</a></li>
            <li><a href="#">Fourth</a></li>
         </ul>
      &lt/li>
      <li><a href="#">Projects</a>
         <ul class="sub-nav">
            <li>a href="#">Project 1</a>
               <ul class="sub-nav1">
                  <li><a href="#">Project 1.1</a></li>
                  <li><a href="#">Project 1.2</a></li>
                  <li><a href="#">Project 1.3</a></li>
                  <li><a href="#">Project 1.4</a></li>
               </ul>
            </li>
            <li>a href="#">Project 2</a>
               <ul class="sub-nav1">
                  <li><a href="#">Project 2.1</a></li>
                  <li><a href="#">Project 2.2</a></li>
                  <li><a href="#">Project 2.3</a></li>
                  <li><a href="#">Project 2.4</a></li>
               </ul>
            </li>
            <li><a href="#">Project 3</a></li>
            <li><a href="#">Project 4</a></li>
         </ul>
      </li>
      <li><a href="#">Contact Us</a></li>
   </ul>
</nav>

Here is what the structure looks like with no CSS applied:




Now to start applying some style. I will include some screenshots so we can see how the menu starts to take shape.

First thing we are going to do is set the color for the background, black:
nav {
   margin-top: 20px;
   background: black;
}



Let's add some color to the hyperlinks.

a:link {
   color: silver;
}


The next block of css will be going after all of the list items in the top-nav class.
.top-nav > li {
   display: inline-block;
   width: 20%;
   position: relative;   
}
Ugh. Looks terrible. What happened is that display: inline-block took all the li elements and rendered them on the same line. This will help with aligning things left to right. Something else to note, the nested ul items, by default a browser will indent those items by 40px. Setting the position to relative sets the positioning context for the 2nd level menu.

Next we will go after all the anchor elements in the list items of the top-nav class.
.top-nav > li a {
   text-decoration: none;
   font-size: 24px;
   line-height: 1.5em;
   text-align: center;
   display: block;  
}


Text-decoration: none removed all the underlines of the anchor elements as well as the bullets. Line-height will center the text vertically. Text-align: center will center the text horizontally. Display: block will render the anchors as block level elements.

Next will be some css that targets hovering over the anchor items for the top-nav, sub-nav, and sub-nav1 classes. The text will simply turn red (look at the Clients tab) and the background of the fly out items (all the Sub Items) will be set to black.
.top-nav > li > a:hover,
.sub-nav > li > a:hover,
.sub-nav1 > li > a:hover {
   color: red;
   background: black;
}


Things will start to take shape with the next block of css. We will target all the sub-lists. The screenshot will not show much due to the sub-lists being hidden.
.sub-nav {
   position: absolute;
   width: 100%;
   visibility: hidden;
   padding-left: 0;
   padding-right: 0;
   margin-left: 0;
   margin-right: 0;
}


Definitely an improvement, starting to look like a proper navigation menu. Visibility: hidden is obviously hiding the lists. Earlier I mentioned that by default a browser will indent lists by 40px, setting the padding and margin to 0 for left will correct that. Setting the width to 100% will make the sub-items the same with as the parent list items. Very, very important...position: absolute will position the ul items directly under the list items. For example, the ul items First, Second, Third, and Fourth will be placed directly under the About item.

We have content that is hidden, let's start showing it. The css below will show sub-nav content when list items of the top-nav class are hovered.
.top-nav li:hover .sub-nav {
   visibility: visible;
}


We have some color and positioning to take care. The next css block will target the list items of the sub-nav class. This code will align the list items nicely and set up the fly out items.
.sub-nav > li {
   display: block;
   width: 100%;
   position: relative;
   background: black;
}


Boom! Look at that, all nice and pretty. Display: block renders each item as a block. Setting the width to 100% will size it to the parent. Setting position to relative is what will assist in the fly out items. And of course, setting the background to black will set the background of the list items to black.

The Clients tab is still a mess. The .sub-nav > li code did help line things up. Let's take a look at it.


The Item1-4 tabs are lined up. The css block below will assist in hiding and positioning the nested items.
.sub-nav1 {
   position: absolute;
   width: 100%;
   visibility: hidden;
   padding-left: 0;
   padding-right: 0;
   margin-left: 0;
   margin-right: 0;
}


The sub-items that will fly out to the right are hidden. Width is set to 100% will size to the parent so things line up. Again, setting the padding and margin left and right will remove the 40px the browser inserts for list items.

Remember the .sub-nav > li css code? It has position: relative set. This is extremely important for the flyouts. The .sub-nav1 code has position set to absolute. The parent is relative and the child is absolute. This limits positioning. An child of a relative parent can be positioned absolutely within that block. Basically, if relative positioning was not set on the parent, setting the child to absolute would cause a drastic change in position, it would not end up where we want it to be. (My head still hurts from that)

Since we are at the meat of the issue, let's break this next part down line by line so we get a really good idea of what is going on. We will hover over the Item 2 list item and make it visible.
.sub-nav li:hover .sub-nav1 {
   visibility: visible;
}


Obviously the positioning is incorrect. If you hover over Item 1 you will not see the sub-items at all, since they are being covered by the black background.

We need to get all the sub-items moved over to the right. To do this, add left: 100%. This will push the items over to the right.
.sub-nav li:hover .sub-nav1 {
   visibility: visible;
   left: 100%;
}


Very nice. The fly out list items are one level down from where we want them to be, we have to move them up. To accomplish this, set top to 0%.
.sub-nav li:hover .sub-nav1 {
   visibility: visible;
   left: 100%;
   top: 0%;
   position: absolute;
}


Looking good! All that remains is to style the list items that are flying out, those are in the .sub-nav1 class. As we have seen many times, set the width to 100% so it is the same size as its parent, and display the list items as blocks. Lastly, set the background to black.
.sub-nav1 > li {
   display: block;
   width: 100%;
   background: black;
}

 



Once you get this far, it's just a pattern now. You could keep on building the navigation menu out to whatever you need.

Sunday, June 30, 2013

jQuery: When Hide Doesn't Hide

I was recently working on a project and the requirement was basically this: A user can have up to five emergency contacts. This was a MVC project. My line of thinking was this....

-The main view model has a property List<EmergencyContactViewModel>.
-EmergencyContactViewModel contained props for name, relationship, home phone, work phone.
-When displaying this in edit mode, there would always be at least one emergency contact.

Given that, you know that you had 4 more contacts to add to the form. I thought, no problem, just add all the fields and then hide them. To my surprise, that did not work. That is the center of this posting.

Here is the abbreviated view:

<div>
   <ul>
      <li><a href="#tabs-1">My Information</a></li>
      <li><a href="#tabs-2">Emergency Contact</a></li>
      <li><a href="#tabs-3">Federal Tax</a></li>
      <li><a href="#tabs-4">State Tax</a></li>
      <li><a href="#tabs-5">Change Request</a></li>
   </ul>
   <div id="tabs-1">
   </div>
   <div id="tabs-2">
   </div>
   <div id="tabs-3">
   </div>
   <div id="tabs-4">
   </div>
   <div id="tabs-5">
      @using (Html.BeginForm())
      {
         <table id="changeRequest">
            <tr>
               <td>Emploee ID</td>
               <td>@Html.TextBoxFor(x => x.MyInfo.EmployeeId)</td>
               <td>Address</td>
               <td>@Html.TextBoxFor(x => x.MyInfo.Address&lt/td>
               <td></td>
               <td></td>
            </tr>
            <tr>
               <td>Last Name</td>
               <td>@Html.TextBoxFor(x => x.MyInfo.LastName)</td>
               <td></td>
               <td><input type="text"></td>
               <td></td>
               <td></td>
            </tr>
            <tr>
               <td>First</td>
               <td>@Html.TextBoxFor(x => x.MyInfo.FirstName)</td>
               <td></td>
               <td><input type="text"></td>
               <td></td>
               <td></td>
            </tr>
            <tr>
               <td>Middle</td>
               <td>@Html.TextBoxFor(x => x.MyInfo.MiddleName)</td>
               <td>City</td>
               <td>@Html.TextBoxFor(x => x.MyInfo.City)</td>
               <td></td>
               <td></td>
            </tr>
            <tr>
               <td>Email</td>
               <td>@Html.TextBoxFor(x => x.MyInfo.Email)</td>
               <td>State</td>
               <td>@Html.TextBoxFor(x => x.MyInfo.State)</td>
               <td></td>
               <td></td>
            </tr>
            <tr>
               <td></td>
               <td></td>
               <td>Zip Code</td>
               <td>@Html.TextBoxFor(x => x.MyInfo.ZipCode)</td>
               <td></td>
               <td></td>
            </tr>
            <tr>
               <td></td>
               <td></td>
               <td>Phone 1</td>
               <td>@Html.TextBoxFor(x => x.MyInfo.Phone1)</td>
               <td></td>
               <td></td>
            </tr>
            <tr>
               <td></td>
               <td></td>
               <td>Phone 2</td>
               <td>@Html.TextBoxFor(x => x.MyInfo.Phone2)</td>
               <td></td>
               <td></td>
            </tr>
            <tr>
               <td><input type="button" id="addContact" value="Add Contact" /></td>
            </tr>
            <tr>
               <td>Contact</td>
               <td>Relationship</td>
               <td>Home Phone</td>
               <td>Work Phone</td>
            </tr>
            @for (var i = 0; i < Model.EmergencyContacts.Count; i++)
            {
               <tr>
                  <td>@Html.TextBoxFor(x => x.EmergencyContacts[i].ContactName)</td>
                  <td>@Html.TextBoxFor(x => x.EmergencyContacts[i].Relationship)</td>
                  <td>@Html.TextBoxFor(x => x.EmergencyContacts[i].HomePhone)</td>
                  <td>@Html.TextBoxFor(x => x.EmergencyContacts[i].WorkPhone)</td>
               </tr>
            }
         </table>
         <input type="submit" value="Submit" />
      }
   </div>
</div>


A few things to point out...
Yes the for loop should be removed and be replaced with an Editor Template. However, this was the standard and how they did things so just keeping in line.

The easiest solution would be that since an employee can have up to five emergency contacts, just render all the fields right out of the gate. Afterall, this is in Edit mode, however, in this post we are going to be doing it dynamically.

Let's take a look at the form...


To do this dynamically, we will need to test for the existence of other rows since an employee can have up to five emergency contacts. If the row exists, of course, don't add. If the row doesn't exist, add a new row:

if ($('#EmergencyContacts_1__ContactName').length < 1) {
   $('#changeRequest > tbody:last').append('<tr id="contact1">' +
      <td><input name="EmergencyContacts[1].ContactName" id="EmergencyContacts_1__ContactName" +
      type="text" /><td> +
      <td>< input name="EmergencyContacts[1].Relationship" id="EmergencyContacts_1__Relationship" +
      type="text" /><td> +
      <td>< input name="EmergencyContacts[1].HomePhone" = id="EmergencyContacts_1__HomePhone" +
      type="text" /><td> +
      <td>< input name="EmergencyContacts[1].WorkPhone" = id="EmergencyContacts_1__WorkPhone" +
      type="text" /><td>
   );
}

if ($('#EmergencyContacts_2__ContactName').length < 1) {
   $('#changeRequest > tbody:last').append('<tr id="contact2">' +
      <td><input name="EmergencyContacts[2].ContactName" id="EmergencyContacts_2__ContactName" +
      type="text" /><td> +
      <td>< input name="EmergencyContacts[2].Relationship" id="EmergencyContacts_2__Relationship" +
      type="text" /><td> +
      <td>< input name="EmergencyContacts[2].HomePhone" = id="EmergencyContacts_2__HomePhone" +
      type="text" /><td> +
      <td>< input name="EmergencyContacts[2].WorkPhone" = id="EmergencyContacts_2__WorkPhone" +
      type="text" /><td>
   );
}

if ($('#EmergencyContacts_3__ContactName').length < 1) {
   $('#changeRequest > tbody:last').append('<tr id="contact3">' +
      <td><input name="EmergencyContacts[3].ContactName" id="EmergencyContacts_3__ContactName" +
      type="text" /><td> +
      <td>< input name="EmergencyContacts[3].Relationship" id="EmergencyContacts_3__Relationship" +
      type="text" /><td> +
      <td>< input name="EmergencyContacts[3].HomePhone" = id="EmergencyContacts_3__HomePhone" +
      type="text" /><td> +
      <td>< input name="EmergencyContacts[3].WorkPhone" = id="EmergencyContacts_3__WorkPhone" +
      type="text" /><td>
   );
}

if ($('#EmergencyContacts_4__ContactName').length < 1) {
   $('#changeRequest > tbody:last').append('<tr id="contact4"> +
      <td><input name="EmergencyContacts[4].ContactName" id="EmergencyContacts_4__ContactName" +
      type="text" /><td> +
      <td>< input name="EmergencyContacts[4].Relationship" id="EmergencyContacts_4__Relationship" +
      type="text" /><td> +
      <td>< input name="EmergencyContacts[4].HomePhone" = id="EmergencyContacts_4__HomePhone" +
      type="text" /><td> +
      <td>< input name="EmergencyContacts[4].WorkPhone" = id="EmergencyContacts_4__WorkPhone" +
      type="text" /><td>
   ');
}


Now it seems the next thing to do would be to add code to hide those table rows and handle the button click event as such:

$('#contact1').hide();
$('#contact2').hide();
$('#contact3').hide();
$('#contact4').hide();

$('#addContact').click(function() {
   $('#contact1').show();
   $('#contact2').show();
   $('#contact3').show();
   $('#contact4').show();
});


Let's go take a look at the result...



The rows are not hidden. The solution is rather simple. All that is needed is to add the visibility attribute with a value of hidden to each table row. For example:

$('#changeRequest > tbody:last').append('<tr id="contact1" style="visibility: hidden;">'


The next step is to handle the click event for the Add Contact button. For each row, change the visibility attribute value to visible, then hide it, then fade in. It may seem odd to show the table row and then hide it again. This is done so we can apply the fade in effect. If you omit hiding the second time, all the fields will just pop in immediately. Of course that is fine, we just want to show a smooth transition:

$('#addContact').click(function () {
   $('#contact1').css('visibility', 'visible').hide().fadeIn('slow');
   $('#contact2').css('visibility', 'visible').hide().fadeIn('slow');
   $('#contact3').css('visibility', 'visible').hide().fadeIn('slow');
   $('#contact4').css('visibility', 'visible').hide().fadeIn('slow');
});


When the page loads, the fields should be hidden...


Then when Add Contact is clicked, the fields should fade in nicely...