Building multiple column displays in MVC

By James at January 12, 2011 16:34
Filed Under: ASP.NET

Looping through a list of objects to display a table of the items is easy in MVC. All you need to do is:

   1: <table>
   2: <%foreach (var item in friendList){%>
   3:   <tr>
   4:     <td><%item.Friend.FullName %></td>
   5:   </tr>
   6: <%}%>
   7: </table>

But what if your display calls for a multi-column layout, say with 4 columns and an unknown number of rows? I have pondered this over and over in many of my projects and have come up with the following code to do this. I have a helper file which my Controller calls to build a string of div tags to display the results. It may look complicated but I will go over the details below.

   1: public string GetAvailableFriends(int memberId)
   2: {
   3:   var list = _repo.List<Member>().Where(m => m.MemberContact.Equals(true))
   4:              .OrderBy(m => m.FirstName).ToList();
   5:   var memberFriends = _repo.List<MemberFriend>()
   6:             .Where(m => m.Member.Id.Equals(memberId)).ToList();
   7:             
   8:   var currentFriends = memberFriends.Select(f => f.Friend).ToList();
   9:  
  10:   var availableFriendList = (from friend in list
  11:                              where !currentFriends.Contains(friend)
  12:                              select new MemberFriends
  13:                             {
  14:                              FirstName = friend.FirstName, 
  15:                              LastName = friend.LastName,
  16:                              FriendId = friend.Id, 
  17:                              FullName = friend.FirstName + " " + friend.LastName,
  18:                              Avatar = friend.Avatar
  19:                              }).ToList();
  20:  
  21:   decimal listCount = availableFriendList.Count;
  22:   var listLimit = Math.Floor(listCount / 4) + 2;
  23:   if (listLimit == 0) listLimit = 1;
  24:   var addedFriends = 0;
  25:             
  26:   var friendTable = new StringBuilder("<div class='availFriends'>");
  27:             
  28:   for (var i = 0; i < listLimit; i++)
  29:   {
  30:      if (addedFriends >= listCount) continue; // we're all done, no need to continue
  31:  
  32:      friendTable.Append(i < listLimit - 1 ? "<div class='row availFriendRow'>" : "<div class='row last'>");
  33:  
  34:      //first column
  35:      if (addedFriends < listCount)
  36:      {
  37:        var friend1 = availableFriendList.ElementAt(addedFriends);
  38:        var avatarSource = _photoHelpers.GetAvatarThumbSource(friend1);
  39:        FriendTableAppend(friendTable, friend1, avatarSource);
  40:        addedFriends++;
  41:      }
  42:  
  43:      //second column
  44:      if (addedFriends < listCount)
  45:      {
  46:        var friend2 = availableFriendList.ElementAt(addedFriends);
  47:        var avatarSource = _photoHelpers.GetAvatarThumbSource(friend2);
  48:        FriendTableAppend(friendTable, friend2, avatarSource);
  49:        addedFriends++;
  50:      }
  51:      
  52:      //third column
  53:      if (addedFriends < listCount)
  54:      {
  55:        var friend3 = availableFriendList.ElementAt(addedFriends);
  56:        var avatarSource = _photoHelpers.GetAvatarThumbSource(friend3);
  57:        FriendTableAppend(friendTable, friend3, avatarSource);
  58:        addedFriends++;
  59:      }
  60:      
  61:      //fourth column
  62:      if (addedFriends < listCount)
  63:      {
  64:        var friend4 = availableFriendList.ElementAt(addedFriends);
  65:        var avatarSource = _photoHelpers.GetAvatarThumbSource(friend4);
  66:        FriendTableAppend(friendTable, friend4, avatarSource);
  67:        addedFriends++;
  68:      }
  69:  
  70:      friendTable.Append("</div>");
  71:   }
  72:   friendTable.Append("<div class='sendFriendRequest sendFriendRequestDisabled'>Send Friend Request</div>");
  73:   friendTable.Append("</div>");
  74:   return friendTable.ToString();
  75: }
  76:  
  77: private void FriendTableAppend(StringBuilder friendTable, MemberFriends friend, string avatarSource)
  78: {
  79:    friendTable.Append("<div class='col col225'>");
  80:    friendTable.Append("<div class='availFriendHolder'>");
  81:    friendTable.Append("<div class='availFriendCheckBox chkGraphic chkUnChecked' fxn='");
  82:    friendTable.Append(friend.FriendId + "'><input type='hidden' name='");
  83:    friendTable.Append(friend.FriendId + "'/></div>");
  84:    friendTable.Append("<div class='availFriendAvatar'><img src='" + avatarSource + "' /></div>");
  85:    friendTable.Append("<div class='availFriendName'>" + friend.FullName + "</div>");
  86:    if(FriendRequestPending(friend.FriendId))
  87:    {
  88:        friendTable.Append("<div class='friendRequestPending'>Friend Request Pending</div>");
  89:    }
  90:    friendTable.Append("</div>");
  91:    friendTable.Append("</div>");
  92: }
  93:  
  94: public bool FriendRequestPending(int friendId)
  95: {
  96:     var approved = false;
  97:     var memberFriend = _repo.List<MemberFriend>()
  98:         .Where(m => m.FriendId.Equals(friendId))
  99:         .ToList().FirstOrDefault();
 100:     if (memberFriend != null)
 101:         approved = memberFriend.Approved;
 102:     return approved;
 103: }

 

  1. The first thing to do is set the variable listCount to how many elements are in the list – line 21
  2. Set a variable, “listLimit” to listCount divided by the number of columns you want, in this case 4 – line 22
  3. If the result comes back as 0 then set it to 1 – line 23
  4. Set a variable “addedFriends” to 0 – line 24
  5. Start a loop – line 28
  6. Inside the loop, grab the element in the list that is indexed at “addedFriends” – lines 37, 46, 55, 64
  7. Call the helper method FriendTableAppend to continue building out the string.
  8. At the end of the loop, finish up the string, then return it.

 

Easy, peasy, nice and cheesy.

Happy Programming from ComponentOne

James

Inland Empire .NET User’s Group Meeting–January 11, 2011-Jeremy Clark

By James at January 12, 2011 11:06
Filed Under: Inland Empire .NET UG

Jeremy Clark from BiTKOO came to present, “Learn to Love Lambdas” to the IEDOTNETUG on Tuesday January 11, 2011. It was a great presentation and really opened the doors on learning how lambdas work and how to best use them.

Jeremy works for BiTKOO, a company specializing in Identity and Access Management. After hours, he puts together demos and technical articles for JeremyBytes.com, a website focusing on .NET technologies.

Attendance: 25

Raffle Prizes: 30

-James

Solving the “A potentially dangerous Request.Form value was detected….” in ASP.NET MVC

By at December 30, 2010 15:57
Filed Under:

Oftentimes an MVC application needs to POST text back that a user has entered into a text field or textarea. In a project I am working on I am using the TinyMCE WYSIWYG text editor to allow users to write articles. The editor allows for HTML tags such as <p>, <strong>, <em>, etc., and when submitting the form I have run across the infamous, “A potentially dangerous Request.Form value was detected” error. So, how do you get around this? You want the user to be able to use some HTML, but you need to secure your site as well from scripting attacks.

Enter the [ValidateInput(false)] attribute.

Adding this attribute to each of the ActionResults where you are expecting HTML tags to come in, will allow the Controller to continue with the action. This will work most of the time but is not exactly bulletproof for protecting your site.

By doing a string.Replace() on the incoming content, and checking for suspicious and/or malicious code, you can secure your site a bit more.

   1: [ValidateInput(false)]
   2: [HttpPost]
   3: public ActionResult Create(Article article, FormCollection collection)
   4: {
   5:   var author = HttpContext.User.Identity.Name;
   6:   var member = _appHelpers.GetAuthenticatedMember(author);
   7:   article.ArticleContent = 
   8:       article.ArticleContent.Replace("<script", "[script")
   9:       .Replace("</script>", "[/script]");
  10:   _repo.Create(article);
  11:   return RedirectToAction("Edit", new {articleId = article.Id});
  12: }

Lines 8 and 9 in the above code will replace “<script>” tags with innocuous [script strings making sure no javascript can run in the code which is submitted. You can also continue along this line to remove any SQL injection attack strings as well.

A nice little method to put in your tool belt. Be sure to use it on your Edit Actions as well.

Happy Programming from ComponentOne

James

POSTing and GETting with jQuery and MVC

By at December 28, 2010 14:30
Filed Under:

The jQuery library has an extensive set of AJAX functions which make it very easy to handle website functionality with all the Ajaxy goodness you’ve come to expect. However there are some gotcha’s when working with MVC and I thought I’d take some time to talk about them.

The first thing to remember is to decorate your Actions with the appropriate Http verbs, either [HttpPost] or [HttpGet]. By default MVC 2 only allows [HttpPost] requests when requesting a Json response. Phil Haack has a great article on this at http://haacked.com/archive/2009/06/25/json-hijacking.aspx.

In a project I am working on I want users to be able to add other users as friends, see if their friend is online, and if necessary, delete a friend. This jQuery function allows a user to add a friend:

   1: function acceptFriend(friendId) {
   2:     var url = "/Member/AcceptFriendRequest/";
   3:     var data = "friendId=" + friendId;
   4:     $.post(url, data,
   5:         function (json) {
   6:             if (json == "success") {
   7:                 //add the div to the My Friends section
   8:                 addNewFriendToList(friendId);
   9:             } else {
  10:  
  11:             }
  12:         }, 'json');
  13: }

You can see here that I am POSTing the data back to the server by defining the url, and any data that needs to be sent as well (lines 2 and 3). Then, depending on the response, add the friend to a friend list. I don’t use an ActionResult but instead use a JsonResult in the Member controller.

   1: [HttpPost]
   2: public JsonResult AcceptFriendRequest(int friendId)
   3: {
   4:   var status = "success";
   5:   try
   6:   {
   7:     var member = _appHelpers.GetAuthenticatedMember(User.Identity.Name);
   8:     var memberId = member.Id;
   9:     var memberFriend = (from mf in _db.MemberFriends
  10:                 where mf.MemberId.Equals(memberId) 
  11:                 && mf.FriendId.Equals(friendId)
  12:                 select mf).FirstOrDefault();
  13:      if(memberFriend != null)
  14:      {
  15:        memberFriend.Approved = true;
  16:        _db.SaveChanges();
  17:      }
  18:    }
  19:    catch(Exception ex)
  20:    {
  21:       status = "false";
  22:    }
  23:    return Json(status);
  24: }

One thing I have discovered that for POST actions you don’t need the [HttpPost] attribute, but I put it in there for readability. The code above is pretty straightforward and not difficult to understand. What caused me a few hours of head scratching was when I was trying to do a GET and find out if a user was online. Two things were standing in my way, and I realized I *needed* to use the [HttpGet] attribute, and override the JsonResult’s default of not allowing GETs. This jQuery checks to see if a user is online.

   1: $.ajax({
   2:   url: "/Member/UserIsOnline/" + friend, //friend is an (int) id defined earlier
   3:   type: "GET",
   4:   success: function (result) {
   5:       if (result == "true") {
   6:          $(friendElement).removeClass("friendOffline").addClass("friendOnline");
   7:       } else {
   8:          $(friendElement).removeClass("friendOnline").addClass("friendOffline");
   9:       }      
  10:    }
  11:  });

The JsonResult in the controller is decorated with [HttpGet]

   1: [HttpGet]
   2: public JsonResult UserIsOnline(int id)
   3: {
   4:  var member = _appHelpers.GetMemberById(id);
   5:  bool isOnline;
   6:  try
   7:  {
   8:    MembershipUserCollection users = Membership.GetAllUsers();
   9:    MembershipUser user = users[member.Email];
  10:    isOnline = user.IsOnline;
  11:  }
  12:  catch (Exception ex)
  13:  {
  14:    isOnline = false;
  15:  }
  16:  return Json(isOnline, JsonRequestBehavior.AllowGet);
  17: }

If you don’t add the JsonRequestBehavior.AllowGet parameter you will get the following error when attempting to run you code:

This request has been blocked because sensitive information could be disclosed to 
third
party web sites when this is used in a GET request. To allow GET requests,
set JsonRequestBehavior to
AllowGet.

Once you get these gotchas out of the way, you’re on your way.

Happy Programming from ComponentOne

James

Uploading photos with MVC

By James at December 22, 2010 13:49
Filed Under:

There are many articles online talking about MVC, but not many showing how to upload files. In a project I’m working on I need to have my users upload photographs and wanted them to be able to preview the photo before submitting it. Here’s a screen shot of what I’m talking about.

7318.image_677BC867

The application allows the user to select a photo, which is then previewed. The user can then add a caption and description of the photo. During this first round the Save button is bound to the following jQuery script:

   1: function uploadMemberPhoto(elem) {
   2:     var $elem = $(elem);    
   3:     $("div.memberPhotoUploader").fadeIn(500);
   4:     var $form = $elem.parents("form");
   5:     $form.attr("action", "/MemberPhotos/Uploaded");
   6:     $elem.fadeOut(500, function () { $form[0].submit(); });
   7: }

which sets the action of the form to “/MemberPhotos/Uploaded”, then submits the form. The Controller action “Uploaded” handles the processing of the image.

   1: public ActionResult Uploaded(HttpPostedFileBase file, FormCollection formCollection)
   2: {
   3:   var memberPhoto = _photoHelpers.UploadMemberPhoto(file, formCollection);  
   4:   return View("UploadedPhoto");
   5: }

_photoHelpers is a helper class which does the processing of the image

   1: public MemberPhoto UploadMemberPhoto(HttpPostedFileBase file, FormCollection collection)
   2: {
   3:   var memberPhoto = new MemberPhoto();
   4:   try
   5:   {
   6:      if(file.ContentLength > 0)
   7:      {
   8:       var member = _repo.Get<Member>(int.Parse(collection["memberId"]));
   9:       var photoCaption = collection["memberPhotoCaption"];
  10:       var photoDescription = collection["memberPhotoDescription"];                    
  11:       var serverPath = HttpContext.Current.Server.MapPath(
  12:              ConfigurationManager.AppSettings["memberAvatarPath"]);
  13:       var memberFolder = GetMemberImageFolder(member);
  14:       var imagePath = serverPath + memberFolder;
  15:  
  16:       if (!Directory.Exists(imagePath))
  17:           Directory.CreateDirectory(imagePath);
  18:  
  19:       bool success = ImageHelpers.ProcessMemberPhotoUpload(file, imagePath);
  20:       if(success) // insert the record
  21:       {
  22:           memberPhoto.MemberId = member.Id;
  23:           memberPhoto.PhotoUrl = file.FileName;
  24:           memberPhoto.PhotoCaption = photoCaption;
  25:           memberPhoto.PhotoDescription = photoDescription;
  26:           memberPhoto.IsApproved = false;
  27:           memberPhoto.IsVisible = true;
  28:           memberPhoto.CreateBy = member.Email;
  29:           memberPhoto.CreateDate = DateTime.Now;
  30:           memberPhoto.ModifyBy = member.Email;
  31:           memberPhoto.ModifyDate = DateTime.Now;
  32:           memberPhoto.Mode = ImageHelpers.ImageMode(file);
  33:           _repo.Create(memberPhoto);
  34:        }
  35:     }
  36:  }
  37:   catch (Exception ex)
  38:   {
  39:     memberPhoto = null;
  40:   }
  41:   return memberPhoto;
  42: }
   1: public bool ProcessMemberPhotoUpload(HttpPostedFileBase img, string fPath)
   2: {
   3:   fPath = fPath.Replace("\\\\", "\\");
   4:   var resizedImage = ResizeImage(img, new Size(640, 480));
   5:   var status = SaveImage(resizedImage, fPath, img.FileName);
   6:  
   7:   var thumbNail = ResizeImage(img, new Size(102, 77));
   8:   status = SaveImage(thumbNail, fPath, "thmb_" + img.FileName);
   9:  
  10:   var largeThumb = ResizeImage(img, new Size(400, 600));
  11:   status = SaveImage(largeThumb, fPath, "lgthmb_" + img.FileName);
  12:   return status;
  13: }

The rest of the methods used in the helper class are here

   1: private static Image ResizeImage(HttpPostedFileBase img, Size size)
   2: {
   3:   var oImage = Image.FromStream(img.InputStream);
   4:   var sWidth = oImage.Width;
   5:   var sHeight = oImage.Height;
   6:             
   7:   if(size.Width >= oImage.Width && size.Height >= oImage.Height)
   8:       return (Image) oImage;
   9:  
  10:   float nPercent = 0;
  11:   float nPercentW = 0;
  12:   float nPercentH = 0;
  13:  
  14:   nPercentW = ((float) size.Width/(float) sWidth);
  15:   nPercentH = ((float) size.Height/(float) sHeight);
  16:  
  17:   if (nPercentH < nPercentW)
  18:      nPercent = nPercentH;
  19:   else
  20:      nPercent = nPercentW;
  21:  
  22:   int dWidth = (int) (sWidth*nPercent);
  23:   int dHeight = (int) (sHeight*nPercent);
  24:  
  25:   Bitmap b = new Bitmap(dWidth, dHeight);
  26:   Graphics g = Graphics.FromImage((Image) b);
  27:   g.InterpolationMode = InterpolationMode.HighQualityBicubic;
  28:   g.DrawImage(oImage, 0, 0, dWidth, dHeight);
  29:   g.Dispose();
  30:   return (Image) b;
  31:  }
  32:  
  33:  private static bool SaveImage(Image img, string savePath, string fileName)
  34:  {
  35:    var status = true;
  36:    try
  37:    {
  38:       var newPath = new StringBuilder(savePath).Append(fileName);
  39:       File.Delete(newPath.ToString());
  40:       img.Save(newPath.ToString(), img.RawFormat);
  41:    }
  42:    catch (Exception)
  43:    {
  44:      status = false;
  45:    }
  46:    return status;
  47: }
  48:  
  49: public string ImageMode(HttpPostedFileBase img)
  50: {
  51:   var oImage = Image.FromStream(img.InputStream);
  52:   var mode = "landscape";
  53:   if (oImage.Width < oImage.Height) mode = "portrait";
  54:      oImage.Dispose();
  55:   return mode;
  56: }

In this particular application, all photos uploaded need to be approved, so after the photo is uploaded, the page refreshes, keeping the original form action of “/MemberPhotos/UpdatePhoto”

8688.image_61FD31F6

   1: public ActionResult UpdatePhoto(MemberPhoto photo)
   2: {
   3:    photo.ModifyBy = User.Identity.Name;
   4:    photo.ModifyDate = DateTime.Now;
   5:    _repo.Edit(photo);
   6: }

You may be wondering how the image is previewed. I found this article at, http: //weblogs.asp.net/imranbaloch/archive/2010/04/03/image-preview-in-asp-net-mvc.aspx and modified it a bit for my needs. This bit of javascript works like a champ.

   1: function ChangeImage(fileId, imageId) {
   2:     $("div.memberPhotoUploader").fadeIn(500);
   3:     $("#form1").ajaxSubmit({ success: function (responseText) {
   4:         if (isImage(responseText)) {
   5:             var d = new Date();
   6:             $(imageId)[0].src = "/ImagePreview/ImageLoad?a=" + d.getTime();
   7:             $(imageId).show();
   8:             $("input#uploadButton").removeClass("btnSaveDisabled")
   9:               .addClass("btnSaveEnabled").removeAttr("disabled").click(function () { 
  10:                 uploadMemberPhoto(this); 
  11:           }); ;
  12:         } else {
  13:             $(imageId).hide();
  14:             $("div.imageTypeNotice").show();
  15:         }
  16:         $("div.memberPhotoUploader").fadeOut(500);
  17:     }
  18:     });
  19: }

Happy programming from ComponentOne

James

Easy control styling with Foxy

By James at December 20, 2010 19:28
Filed Under: Foxy, Visual Style Designer, Design

Using third party controls like ComponentOne’s tools makes building your websites faster and easier. And, with all the themes that the C1 tools have, chances are you can find one which blends nicely. But if you can’t then what do you do? You really don’t want to go digging through all the CSS and graphics files to make a few changes, nor do you want to make your site match the theme you’ve chosen. Well fret no more, along comes Foxy.

5417.image_51FE523C

C1’s Foxy allows you to edit a theme from Studio for ASP.NET, or even create your own theme to use with the Studio for ASP.NET components. You can choose to edit just a few controls or the entire theme’s suite, drilling down as far as you want to. More than likely all you really need to do is just change the colors slightly, and this is where Foxy really makes it easy for you by allowing you to change the HLS properties of the theme. Here’s how to do it.

1. Open Foxy. It will be in your Start Menu at ComponentOne, Studio for ASP.NET AJAX, Style Designer, Foxy for ASP.NET

7571.image_49DF5247

2. I have found that “ArticFox” is the best one to modify via the Colorize feature as it is the most bland so all the elements will look good when colorized. From the VisualStyles window, select a ArticFox and drag it down to the Custom VisualStyles window

8231.image_155E7FFF

3. Expand the ArcticFox-Custom1 node, then double-click one of the controls. I am going to continue this using the C1Calendar. If you plan to only use a few controls in your project, select, then delete, the controls in this panel you won’t be using. Otherwise when you import into your project all of them will go across as well.

8463.image_251E1503

4. Double-clicking the control, opens the control in the Foxy designer surface, showing the hierarchy of the control’s elements, the control itself and the CSS for the control

2045.SNAGHTMLde2d4c4_1843BEE5

5. For now, just select the C1Calendar node in the upper left corner of the screen, then click on the Colorize button in the tool bar towards the upper right.

0451.image_1475F448

6. This brings up the Hue/Saturation dialog window

1057.SNAGHTMLde65d16_250DEF36

7. Click on the Colorize check box. You can then choose a color, or make changes to the current color with the Hue, Saturation and Lightness sliders. I’ve selected the teal color, (4 over, 3 down).

3005.image_716582D7

8. In the Hue/Saturation dialog window, start playing with the sliders to colorize the theme. You will see the changes in real time.

3201.image26_7A720E58

9. Setting the values to Hue: 124, Saturation: 35, Lightness: –46 makes a nice Olive color. Click on Apply and you will see your changes in the designer pane.

3288.image_2D619EC0

What’s really nice about the Colorize option is that it takes care of the graphics as well, giving them the same colors as the HTML elements.

The C1Calendar control, as most of the other C1 controls are set to inherit the font family of your own style sheet. However they may be times when you want the control to have a bit different font – a little bigger, bold perhaps, maybe a different color – and this is another area where Foxy makes it easy.

1. In the left hand control panel, select the “Control” node, just after the parent node, “C1Calendar”

2. In the Ribbon Font bar, select a font family from the drop down. The changes will be made in real time so you can play around with the different settings to get just the look you want.

5672.image_302753B3

When you get all your changes made, click on the Save button. So, we made this nice theme for our calendar, now how do we get it into our project?

1. In your ASP.NET Web Application Solution Explorer, right click on the project name and select Add, New Folder. Name the new folder, “VisualStyles”, then right click on the new folder and select Add, New Folder, naming this folder the name of the theme you just created. On this new folder right click again and select Add, New Folder, naming this one C1Calendar.

5556.image_039E23CD

2. Add the custom css file you created above by right clicking on C1Calendar then selecting Add, Existing Item. Navigate to where Foxy saves your files on your computer, typically “C:\Users\<UserName>\Documents\C1FoxyVSD\<Custom Theme Name>\<Control Name>. In this case the folder I am looking for is “C:\Users\James\Documents\C1FoxyVSD\ArcticFox-Custom1\C1Calendar\styles.css”. Select the file, then click on Add.

3. We need to add the images for the Calendar control as well, so right click on the C1Calendar folder in the Solution Explorer and select Add, New Folder, naming it “Images”. Right click on the new “Images” folder and select Add, Existing Item, then navigate to the same folder as you went to in Step 2 above, “C:\Users\James\Documents\C1FoxyVSD\ArcticFox-Custom1\C1Calendar\Images”. Select the two files, “calendar.png” and “calendar-repeater.png”, then click on Add.

4. Add a C1 Calendar control to your page, by dragging it from the Toolbox to the aspx page in Visual Studio, then click on the Design tab in the lower left corner of Visual Studio.

5. Click the Calendar control’s Task button to bring up the Task dialog

4774.image_33E4F883

6. Change the VisualStylePath to ~/VisualStyles, this is the folder you created in step 1

7. Uncheck the UseEmbeddedVisualStyles check box

8. Click in the VisualStyle drop down and your Custom style will be present. Select it

6136.image_2AA8BD42

9. Run your application and you will see the calendar rendered with the new Visual Style

2806.before_6855A4FE 7840.after_0653B2F3
Before styling with Foxy After styling with Foxy

In this article we talked about how easy it is to use ComponentOne’s controls. We also talked about how using the Visual Style designer, Foxy, can make it really simple to make small changes to the controls to add your own colors and emphasis. Foxy is a really great tool, that can make your development even more fun and productive.

Happy Programming,

James

jQuery and MVC–Part 4, now with Wijmo!

By James at December 17, 2010 11:04
Filed Under: JavaScript, jQuery, jQuery Plugins, Wijmo

In part three of this series I talked about how to extend jQuery with plugins, went through the steps of writing a plugin, and then demonstrated how to use it in a standard HTML page. I showed you a popular Table Sorter plugin and showed how to initialize it with different options. In this last article I will show you one more plugin, the Wijmo Grid, and how easy it easy to implement it’s different options and features. I will also show you some of the cool things that Wijmo can do to help spruce up your web forms and pages.

Going back to the Student Course Admin form I’ve been using for these articles, I am generating a table of student data in an ASP.NET MVC app.

7563.image_281CC156

One thing I did add since the last article was the ability to filter the students by their first or last name.

4186.image_2BDA6626

3666.image_43919D8C

This was done by adding some JavaScript and an Ajax call which gets a new set of data from the server, then replaces the original table with the new table.

   1: function getStudentsByName(elem) {
   2:     $("img.ajaxLoaderFName").fadeOut(500);
   3:     $("img.ajaxLoaderLName").fadeOut(500);
   4:     var $elem = $(elem);
   5:     var url = "";
   6:     var ajaxLoader;
   7:     if ($elem.attr("id") == "txtFilterFName") {
   8:         url = "/Home/GetStudentsByFirstName/" + $elem.val();
   9:         ajaxLoader = $("img.ajaxLoaderFName");
  10:     }
  11:     if ($elem.attr("id") == "txtFilterLName") {
  12:         url = "/Home/GetStudentsByLastName/" + $elem.val();
  13:         ajaxLoader = $("img.ajaxLoaderLName");
  14:     }
  15:     
  16:     ajaxLoader.fadeIn(500);
  17:     $.ajax({
  18:         url: url,
  19:         type: "GET",
  20:         success: function (result) {
  21:             var holder = $("table.studentTable").parent("div.row");
  22:             $("table.studentTable").remove();
  23:             holder.html(result);
  24:             ajaxLoader.fadeOut(500);
  25:             $("div.chkGraphic").graphicCheckBox();
  26:         }
  27:     });
  28: }

Pretty standard stuff by now, but it can be made a whole lot better by using Wijmo. Let’s see how to do that.

Wijmo

Wijmo is a new product, complete kit of over 30 jQuery UI Widgets. It is a mixture of jQuery, CSS3, SVG, and HTML5. You can download it at www.wijmo.com.

The Wijmo grid has 20 different options that you can mix and match to make your grid look and behave how you would like. Detailed information about these options can be found at http://wijmo.com/wiki/index.php/Grid#ui.wijgrid_Options. For this demo, I am going to setup the grid with the following options: allowSorting, allowColumnSizing, allowPaging, and setting the page size to 10 rows.

As I mentioned before, when adding the references to your JavaScript files, the order of each reference is very important. After you download Wijmo, there will be several JavaScript files which will need to be included in your page.

   1: <script type="text/javascript" 
   2:   src="[Path]/Wijmo-Open/development-bundle/external/
   3:   jquery-1.4.3.min.js"></script>
   4:  
   5: <script type="text/javascript" 
   6:   src="[Path]/Wijmo-Open/development-bundle/external/
   7:   jquery-ui-1.8.6.custom.min.js"></script>
   8:  
   9: <script type="text/javascript" 
  10:   src="[Path]/Wijmo-Open/development-bundle/external/
  11:   jquery.bgiframe-2.1.3-pre.js"></script>
  12:  
  13: <script type="text/javascript" 
  14:   src="[Path]/Wijmo-Open/development-bundle/external/
  15:   jquery.glob.min.js"></script>
  16:  
  17: <script type="text/javascript" 
  18:   src="[Path]/Wijmo-Complete/development-bundle/external/
  19:   jquery.mousewheel.min.js"></script>
  20:  
  21: <script type="text/javascript" 
  22:   src="[Path]/Wijmo-Complete/development-bundle/external/
  23:   jquery.tmpl.min.js"></script>
  24:  
  25: <script type="text/javascript" 
  26:   src="[PathToJavaScript]/Wijmo-Complete/development-bundle/external/
  27:   raphael.js"></script>
  28:  
  29: <script type="text/javascript" 
  30:   src="[PathToJavaScript]/Wijmo-Open/js/
  31:   jquery.wijmo-open.1.0.0.min.js"></script>
  32:  
  33: <script type="text/javascript" 
  34:   src="[PathToJavaScript]/Wijmo-Complete/js/
  35:   jquery.wijmo-complete.1.0.0.min.js"></script>

You will also need to reference the Wijmo style sheets as well.

   1: <link href=”[Path]/Wijmo-Complete/development-bundle/themes/
   2:   ui-lightness/jquery-ui-1.8.4.custom.css" rel=”stylesheet
   3:   type=”text/css/>
   4:  
   5: <link href=”[Path]/Wijmo-Open/css/jquery.wijmo-open.1.0.0.css" 
   6:   rel=”stylesheettype=”text/css/>
   7:  
   8: <link href=”[Path]/Wijmo-Complete/css/jquery.wijmo-complete.1.0.0.css" 
   9:   rel=”stylesheettype=”text/css/>

Initializing the Wijmo grid is done the same way you do other things in jQuery, by getting an element in the page, in this case the table with an ID of “studentTable”, then adding the jQuery plugin to the element.

   1: $("#studentTable").parent().wijgrid({ 
   2:     allowSorting: true, 
   3:     allowColSizing: true, 
   4:     allowPaging: true, 
   5:     pageSize: 10 
   6: });

Here is the result.

0027.image_27A06894

As you can see the graphical check buttons I talked about in the last article are still there, and the grid has a nifty pager at the bottom.

The allowSorting option adds sorting capability on each column as seen here.

4578.image_52985699

While the “allowColSizing” lets you adjust the widths of each column to better view your data.

1300.image_1A9662B6

Another great option with the Wijmo grid is to allow editing to your data. In the jQuery to initialize the Wijmo grid, add another option, allowEditing:true.

   1: $("#studentTable").wijgrid({ 
   2:     allowSorting: true, 
   3:     allowColSizing: true, 
   4:     allowPaging: true, 
   5:     pageSize: 10, 
   6:     allowEditing: true 
   7: });

When the grid is rendered, each table cell will now become editable. All you have to do is click in the cell and start making any changes you need. The Wijmo Grid exposes events so you can handle the changed data however you prefer to.

There are 15 events total in the Wijmo Grid, and details on each of them are at http://wijmo.com/wiki/index.php/Grid#ui.wijgrid_Events. The Wijmo grid is very feature rich and I would highly recommend you play around with it.

Other Wijmo goodies

No more do you have to deal with boring old form elements on your page. Wijmo has decorators for Radio Buttons, Checkboxes, Drop Downs and Text fields. Adding these decorators is as easy as adding an additional jQuery call to your selected element.

So, by adding the following to my text fields which filter by student name.

   1: $("input#txtFilterFName").focus(function () { 
   2:     $("input#txtFilterLName").val(""); }).keyup(function () { 
   3:     getStudentsByName(this); }).wijtextboxdecorator();
   4:  
   5: $("#chkPaging").wijcheckboxdecorator();
   6:     $("#txtPageSize").wijtextboxdecorator();
   7:     $("input[type=radio]").wijradiobuttondecorator();

I get these spiffy looking text fields and form elements.

7484.image_52F463C1

One thing you may have noticed is that the grid and the form elements all have the same theme. This is because of the jQuery UI theming that is built into Wijmo. If you don’t like how the elements look you can create your own themes with the jQuery UI Theme Roller at http://jqueryui.com/themeroller/. When you get your theme just how you want it to, all it takes is a download, and referencing your theme’s particular style sheet in your page.

7026.image_43051EFD

   1: <link href="[Path]/Content/ui-lightness/jquery-ui-1.8.4.custom.css"
   2:     rel="stylesheet" type="text/css" />

In this article we talked more about jQuery plugins, the Wijmo gallery of widgets and how they can be used to create dynamic and very professional looking web applications. I hope you have enjoyed this series and have learned a few tricks. And as always, I would love to read your comments on my blog.

Happy Programming,

James

New Mexico .NET Users Group

By James at December 06, 2010 18:58
Filed Under: user groups, community

Last Thursday, December 12, I had the opportunity to go to Albuquerque, New Mexico and present at the New Mexico .NET Users Group. My flights were uneventful, checking into the hotel a breeze, and I got a free upgrade on the rental car, all that was left was to meet the group and give them my presentation, “ASP.NET MVC, jQuery, Ajax, EF and some other groovy stuff thrown in”. After the typical networking between members, the meeting started promptly at 7:00 pm, with some announcements by the president Tom Murdock. I was expecting a typical length meeting, but had been told just prior that their meetings usually go to 8:30 pm. Uh-oh I think, “two and half hours in 75 minutes”. Knowing that I would have less time I sneakily started my presentation a bit early, asking the usual questions I start off with – number of web and windows developers, C# vs. VB, who does their own database admin, etc.

My presentation turned into more of a lightning talk and actually it turned out pretty well. There was still time for audience questions and comments, I was focused on the code and concept so there wasn’t any time for tangents, and even had a little back and forth with one young kid who kept teasing me about not using PHP. I was able to tease him back and the whole presentation turned out very enjoyable. And as it turned out, I was able to go until 9:00 pm.

The attendees treat each other like family members, which was great to see. One of the things I noticed, and bumped up my ego several notches was to see many of the attendees taking notes on what I said. I added several more twitter followers, and three people actually commented on my presentation at www.speakerrate.com. Tom has grown a great group and should be congratulated for all his accomplishments. Best of all, they invited me back for another presentation.

All in all, one of the best visits I’ve had!

Here’s some pictures

4380.IMG_2511_277FED2C 0676.IMG_2512_248A8879
5621.IMG_2513_687E46C3 1207.IMG_2514_30E885D5
1374.IMG_2515_75487714  
Tom Murdock – right and ComponentOne Studio winner, James Archuleta  

Happy Programming

James

jQuery and MVC–Part 3

By James at November 19, 2010 19:11
Filed Under: JavaScript, MVC, jQuery, jQuery Plugins

In the last article I talked about MVC and doing some nifty Ajax functionalities with jQuery. In this article I will go over jQuery plugins, what they are and how to create them.

jQuery Plugins

jQuery has a plugin architecture which allows you to package up commonly used functions into a plugin. Chances are if you have a complex JavaScript function, someone has more than likely written a plugin to handle that particular functionality. The jQuery.com site has an extensive plugin library and it can sometimes be fun to browse through it to see what people have come up with.

Plugins are fairly easy to write, once you have gotten the basics of jQuery down. Here is a tooltip plugin that I wrote in just a few minutes.

   1: (function ($) {
   2:     $.fn.tooltip = function (options) { // make sure to name the function
   3:                                         // the same as your tooltip
   4:         var methods = {
   5:             
   6:             //initialize stuff
   7:             init: function (options) { },
   8:             
   9:             //destroy and clean up
  10:             destroy: function () {
  11:                 return this.each(function () {
  12:                     $(window).unbind("tooltip");
  13:                 })
  14:             }
  15:         };
  16:  
  17:         // default settings if they aren't passed in
  18:         var settings = {
  19:             'backgroundColor': 'red',
  20:             'color': 'blue',
  21:             'toolTipText': 'Hi',
  22:             'fontWeight': 'bold'
  23:         };
  24:  
  25:         // add the settings into the options
  26:         return this.each(function () {
  27:             if (options) {
  28:                 $.extend(settings, options);
  29:             }
  30:  
  31:             var $this = $(this);
  32:             $this.mouseenter(function (event) {
  33:                 $this.css("color", settings.colorEnter);
  34:                 
  35:                 // if the div#toolTip isn't in the DOM, create a new one
  36:                 if (!$("#toolTip").length) {
  37:                     // create the element
  38:                     $('<div id="toolTip"></div>')
  39:                         // set the text to passed option
  40:                         .html(settings.toolTipText)
  41:                         // from the style sheet
  42:                         .addClass("toolTip")
  43:                         // insert the div into the DOM just after $this
  44:                         .insertAfter($this)
  45:                         // position the div
  46:                         .css({ "left": $this.width(),
  47:                         // set the backgroundColor
  48:                         "backgroundColor": settings.backgroundColor,
  49:                         // set the font
  50:                         "fontWeight": settings.fontWeight,
  51:                         // set the color
  52:                         "color": settings.color
  53:                     }); 
  54:                 }
  55:                 $("#toolTip").fadeIn(500);
  56:             });
  57:  
  58:             $this.mouseleave(function () {
  59:                 $this.css("color", settings.colorLeave);
  60:                 $("#toolTip").fadeOut(500);
  61:             });
  62:         });
  63:     };
  64: })(jQuery);

It’s not a pretty thing, or even that functional, but it does show off how fast you can write a plugin. Let’s take a look at how this plugin is implemented.

1. In the <head> tag of your page include the references to your jQuery and JavaScript files.

   1: <script src="Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
   2: <script src="Scripts/jquery.tooltip.js" type="text/javascript"></script>
   3: <script src="Scripts/AppScripts.js" type="text/javascript"></script>

2. Somewhere on your page add an element that will call the plugin

   1: <div class="test">This is the element that will have the assignment</div>

3. In your custom JavaScript, attach the plugin to the element and pass in any options you need to.

   1: $(document).ready(function () {
   2:     $("div.test").tooltip({ "toolTipText": "Hello from ComponentOne", 
   3:         "color":"white" })
   4:         .click(function () { alert("hi"); });
   5: });

4. Add the following to your style sheet to style up the test div.

   1: .test{width:200px;
   2:    float:left;background:silver;
   3:    color:White;border:1px solid White;
   4:    cursor:pointer;padding:5px}

5. Open the page in a browser, then hover over the test div. If all goes well, you should see the following.

 

1447.image_12F3BB96

5287.image_2A3EC007

Obviously this isn’t the world’s greatest plugin, but does cover the basics of getting started. Information on building more functional plugins can be found at http://docs.jquery.com/Plugins/Authoring

TableSorter, a more functional plugin

Since we are working with tabular data in this application, I want to use a plugin that will modify an HTML table and make it sortable. Searching through the plugins library I found “TableSorter” at http://tablesorter.com. It is written by Christian Bach at www.lovepeacenukes.com and does a great job of providing a pretty nifty data grid from a static HTML Table.

Let’s take a look.

2744.image_3AD6BAF5

In the head tag of the HTML file place a reference to the TableSorter plugin right after the reference to the jQuery file.

2474.image_05114FCE

In the View in which you want to show the table, write your code to render a table with the data you want to show. One thing about the TableSorter plugin is that it requires valid HTML tables complete with <thead> and <tbody> tags. The following markup is written with the new MVC 3 Razor View Engine.

   1: <table id="studentTable" class="studentTable {sortlist: [[6,0]]}">
   2:     // sortlist is a function of the plugin which automatically sorts
   3:     // on a specific column
   4:     <thead>
   5:     <tr>            
   6:         <th>First Name</th>
   7:         <th>Last Name</th>
   8:         <th>Email</th>
   9:         <th class="{sorter: false}">Comments</th> 
  10:         // sorter class is a plugin option
  11:         <th>Submit Date</th>
  12:         <th>Approved Date</th>
  13:         <th>Approved</th>
  14:     </tr>
  15:     </thead>
  16:     <tbody>    
  17:     @foreach (var item in Model)
  18:     {        
  19:         var approvedDate = string.Empty;
  20:         if (item.ApprovedDate.HasValue) { 
  21:             approvedDate = item.ApprovedDate.Value.ToShortDateString(); 
  22:         }
  23:           <tr>               
  24:             <td>@item.FirstName</td>
  25:             <td>@item.LastName</td>
  26:             <td>@item.Email</td>
  27:             <td>@item.Comments</td>
  28:             <td>@item.SubmitDate.ToShortDateString()</td>
  29:             <td>@approvedDate</td>
  30:             <td>
  31:               <span style="display:none" class="hiddenSpan">@item.Approved</span>
  32:               @Html.Hidden("Approved", item.Approved)
  33:               @Html.Hidden("studentId", item.Id)
  34:               @{var chk = "chkGraphic chkUnChecked";}
  35:               @if (item.Approved.HasValue && item.Approved.Value.Equals(true))
  36:               {
  37:                   chk = "chkGraphic chkChecked";
  38:               }
  39:               <div class="@chk"></div>
  40:               <img src="../../Content/Images/ajax-loader.gif" 
  41:               class="ajaxLoader ajaxLoaderSmall" />
  42:             </td>
  43:         </tr>
  44:     }    
  45:     </tbody>
  46: </table>

Since we are rendering a table, we can use the $(document).ready(function(){});

Add the following in your custom JavaScript file:

   1: /// <reference path = "/Scripts/jquery-1.4.1-vsdoc.js"/>
   2:  
   3: $(document).ready(function () {
   4:     $("#studentTable").tablesorter({ widgets: ['zebra'] });
   5:     // zebra is a “widget” included in the plugin which will alternate
   6:     // the row colors of the table
   7: });

Run the page and sort the columns. One thing to note in the markup is in the last column. The plugin sorts on the data contained in the columns of each row. Since I want to sort on “Approved”, I add a hidden field with that data as the first element in the cell.

7024.image_5BBA07CF

2133.image_5713D748

The TableSorter plugin has many more options than this brief demo show. If you want to explore it more, download it at http://tablesorter.com.

A Freebie

If you look at lines 30 - 42 in the markup you may be wondering what all the other stuff is, including the <div class=”@chk”></div>. I wanted to spiff up the form and use graphics for the checkboxes, but still keep the functionality of a checkbox. Here is how to do it.

1. In the style sheet add the following classes.

   1: .chkGraphic{cursor:pointer;width:22px;height:22px;float:left;margin-right:5px}
   2: .chkChecked{background:url(Images/checkBoxOn.png)no-repeat}
   3: .chkUnChecked{background:url(Images/checkBoxOff.png)no-repeat}

2. In your custom JavaScript file, add the following functions.

   1: $("div.chkGraphic").live("click", function () { toggleGraphicCheckBox(this); });
   2:  
   3: function toggleGraphicCheckBox(elem) {
   4:     var $elem = $(elem);
   5:     $elem.siblings("img.ajaxLoader").show();
   6:     var valueField = $elem.siblings("input[type=hidden][name=Approved]");
   7:     if ($elem.hasClass("chkChecked")) {
   8:         $elem.removeClass("chkChecked").addClass("chkUnChecked");
   9:         valueField.val("False");
  10:     } else {
  11:         $elem.removeClass("chkUnChecked").addClass("chkChecked");
  12:         valueField.val("True");
  13:     }
  14:     $("span.hiddenSpan").html(valueField.val());
  15:     toggleStudentApproval($elem);
  16: }
  17:  
  18: function toggleStudentApproval(elem) {    
  19:     var approved = elem.siblings("input[type=hidden][name=Approved]").val();
  20:     var studentId = elem.siblings("input[type=hidden][name=studentId]").val();
  21:     $.ajax({
  22:         url: "/Home/GetStudent/" + studentId + "/" + approved,
  23:         type: "GET",
  24:         success: function (result) {
  25:             showStudentApproval(result, elem);
  26:         }
  27:     });
  28: }
  29:  
  30: function showStudentApproval(result, elem) {    
  31:     var id = result.Id;
  32:     var td = elem.parent("td").prev("td");
  33:     td.html("");
  34:     if (result.ApprovedDate != null)
  35:         td.html(result.ApprovedDate);
  36:     elem.siblings("img.ajaxLoader").fadeOut(500);
  37: }

Here’s the markup again.

   30: <td>
   32:  <span style="display:none" class="hiddenSpan">@item.Approved</span>
   33:  @Html.Hidden("Approved", item.Approved)
   34:  @Html.Hidden("studentId", item.Id)
   35:  @{var chk = "chkGraphic chkUnChecked";}
   36:  @if (item.Approved.HasValue && item.Approved.Value.Equals(true))
   37:  {
   38:     chk = "chkGraphic chkChecked";
   39:  }
  40:  <div class="@chk"></div>
  41:  <img src="../../Content/Images/ajax-loader.gif" 
  42:        class="ajaxLoader ajaxLoaderSmall" />
  43: </td>

Now based on what you’ve read so far in this and the previous articles, take a look at all the code and see if you can figure out what will happen when the graphical checkbox is clicked. Add a comment to this article and let me know what you think it will do.

Just for grins and giggles, I converted the JavaScript above to a plugin, jquery.graphicCheckBox.js

   1: (function ($) {
   2:     $.fn.graphicCheckBox = function (options) {
   3:     var methods = {
   4:         // initialize stuff
   5:         init: function (options) { },
   6:         //destroy and clean up
   7:         destroy: function () {
   8:             return this.each(function () {
   9:                 $(window).undbind("graphicCheckBox");
  10:             })
  11:         }
  12:     };
  13:     // default settings if they aren't passed in
  14:     var settings = {
  15:         'checked': 'chkChecked',
  16:         'unchecked': 'chkUnChecked',
  17:         'urlToAction': '/Home/GetStudent/'
  18:     };
  19:         // add the settings into the options
  20:     return this.each(function () {
  21:         if (options) {
  22:             $.extend(settings, options);
  23:         }
  24:  
  25:         var $this = $(this);
  26:         $this.click(function (event) {
  27:             $this.siblings("img.ajaxLoader").show();
  28:             var valueField = $this.siblings("input[type=hidden][name=Approved]");
  29:             if ($this.hasClass(settings.checked)) {
  30:                 $this.removeClass(settings.checked).addClass(settings.unchecked);
  31:                 valueField.val("False");
  32:             } else {
  33:                 $this.removeClass(settings.unchecked).addClass(settings.checked);
  34:                 valueField.val("True");
  35:             }
  36:             $("span.hiddenSpan").html(valueField.val());
  37:             toggleApproval($this);
  38:         });
  39:     });
  40:     function toggleApproval(elem) {
  41:         var approved = elem.siblings("input[type=hidden][name=Approved]").val();
  42:         var id = elem.siblings("input[type=hidden][name=studentId]").val();
  43:         $.ajax({
  44:             url: settings.urlToAction + id + "/" + approved,
  45:             type: "GET",
  46:             success: function (result) {
  47:                 showApproval(result, elem);
  48:             }
  49:         });
  50:     }
  51:     function showApproval (result, elem) {
  52:         var id = result.Id;
  53:         var hid = elem.siblings("input[type=hidden][name=studentId]").val();
  54:         var td = elem.parent("td").prev("td");
  55:         td.html("");
  56:         if (result.ApprovedDate != null)
  57:             td.html(result.ApprovedDate);
  58:         elem.siblings("img.ajaxLoader").fadeOut(500);
  59:     }
  60: }
  61: })(jQuery);

In this article we talked about extending jQuery with plugins, how to write a simple plugin, and explored a fully functional table sorter plugin. I even threw in a groovy freebie for you.

Happy Programming,

James

San Gabriel Valley Developers Group

By James at November 18, 2010 17:47
Filed Under: user groups, evangelism

Last night (November 18, 2010) I had the privilege of presenting at the San Gabriel Valley Developers Group run by Adnan Masood and Richard Trinh. I gave my usual presentation on MVC and EF, and decided to jazz it up with some jQuery and Ajax. There was a wide variety of expertise in the group, so everyone got something out of the presentation. About half were really interested in the Ajax, the other in the data layer and what EF can provide. There was even some time to dig deep into EF, pretending there was a rogue DBA who kept changing the database schema and how EF can keep up with it. Lot’s of fun.

As usual, I peppered the members with questions, and tossed out the famous ComponentOne carabineer key chains. Two people actually got my references to POCO and EF Contexts “remaining the same” to late ‘70’s bands. There was a great back and forth with the members and a ton of great questions. A win-win all around.

Adnan and Richard do a bang up job of running the group, and deserve big pats on the back for all they do. If you are ever in Monrovia, CA on the 3rd Wednesday of the month and need to get your geek on, this is the place to be.

Here’s some pictures.

8168.sgv1_2FB78B77 3326.sgv2_01BA12F2
3365.sgv3_3B2CFD1C 4276.sgv4_6D1477D9
3603.sgv5_2C62059D 1108.sgv6_3F730C7C
   
8311.c1-winner-Richard-Chin_273F7C54
ComponentOne Studio Winner, Richard Chin with Adnan Masood

Happy programming,

James

About the author

James James is a five time and current Microsoft MVP in Client App Development, a Telerik Insider, a past Director on the INETA North America Board, a husband and dad, and has been developing software since the early days of Laser Discs and HyperCard stacks. As the Founder and President of the Inland Empire .NET User's Group, he has fondly watched it grow from a twice-a-month, early Saturday morning group of five in 2003, to a robust and rambunctious gathering of all types and sizes of .NET developers.

James loves to dig deep into the latest cutting edge technologies - sometimes with spectacular disasters - and spread the word about the latest and greatest bits, getting people excited about developing web sites and applications on the .NET platform, and using the best tools for the job. He tries to blog as often as he can, but usually gets distracted by EF, LINQ, MVC, ASP, SQL, XML, and most other types of acronyms. To keep calm James plays a mean Djembe and tries to practice his violin. You can follow him on twitter at @latringo.

And as usual, the comments, suggestions, writings and rants are my own, and really shouldn't reflect the opinions of my employer. That is, unless it really does.

James Twitter Feed

Recent Comments

Comment RSS

Month List