Monday, 21 December 2009

Feedback module like MSDN in Asp.net MVC

Aim

Create a feedback module similar to the new one MSDN is using done in the asp.net MVC framework. This will including using some JQuery. Also I want to make it as steam lined and unobtrusive as possible. The functionality has to be user friendly but not interrupt current workflow.

image

This is the challenge … lets see how it goes!

Code

So the first issue which needed to be addressed was “how to get the view displayed?”. For this I initially turned to the Html.RenderAction() helper method and to render a partial view. I decided against this quite early on due to the restriction of posting actions back to the view which was nested into the main view. This seemed to cause issues with post backs and just felt wrong.

So decided to go the System.Web.Mvc.PartialViewResult route instead. To use this, the feedback controller required to return the PartialView for the actions which are required for this module of functionality.

Next was the flow of the interaction with the user; from clicking the initial link in the corner of the browser to submitting the comment, thanking the user and finally returning back to the original state.

To keep it simple, I kept the feedback model class very simple. This wasn’t the primary aim of this task so being able to submit a comment was good enough for this exercise.

namespace MvcApplication3.Models
{
public class Feedback
{
public string Comment { get; set; }
public DateTime Logged { get; set; }
public string UserAgent { get; set; }
public string Ip { get; set; }
}
}



Like I said, very simple :-)

So, time for the controller. Due to the flow I needed to display the feedback partial view, display the thanks partial view and the actual submit action code with the model binding.



using System.Web.Mvc;
using MvcApplication3.Models;

namespace MvcApplication3.Controllers
{
public class FeedbackController : Controller
{
//
// GET: /Feedback/

[HttpGet]
public ActionResult Get()
{
return PartialView("Feedback");
}

[HttpGet]
public ActionResult Thanks()
{
return PartialView("FeedbackThanks");
}

[HttpPost]
public void Submit(Feedback feedback)
{
// update to the date required for logging
feedback.Logged = DateTime.Now;
feedback.UserAgent
= Request.UserAgent;
feedback.Ip
= Request.UserHostAddress;

// and save

}
}
}



I limited the http verbs on the actions so that they are restricted properly. So, all the c# code has been written. Looking at this it seems straight forward and relatively simple :-)

All the views would need to be accessed from a central location, so I put them in the shared view folder (see solution explorer view below).



image



Site.Master



The site master is where the feedback button is situated as it appears on each of the pages which uses this master page. From a markup perspective we needed to add the button div itself and the div which the partial views will be loaded into.





    <div class="feedback-button" title="Click here to tell us what you think!"><a id="feedbackLaunch">feedback</a></div>

<div id="feedbackformDiv"> </div>




image



Where the magic happens is in the jQuery client side script. Instead of trying to explain it, I’ll just post it as we go and go through the highlights as the rest will be self explanatory.



        // global flag to display the feedback routine
var displayFeedback = true;

$(document).ready(
function() {

// display the feedback form
$('#feedbackLaunch').click(function() {

// depending on the feedback for, hide or show it
if (displayFeedback) {
$(
'#feedbackformDiv').load('/Feedback/Get').fadeIn("slow");
}
else {
$(
'#feedbackformDiv').hide();
}

// flip the bit
displayFeedback = !displayFeedback;
});
});


The main driving force of visibility is the displayFeedback variable. This has been declared at this level so that its scope is accessible from the partial views as well. The reason why I did this was that once the interaction flow had occured a double click on the feedback button was required to display the comment form again. This wasn’t required behaviour. I tried using the jQuery toggle functionality but this didn’t perform as required either.



Feedback.aspx



The partial view is where the main action happens. This is the main display form which prompts the user for the comment and to submit it.



imageThis is made up of the header which has a close button on it, and the main form with a simple submit button. The markup and script of the file is below:



<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<MvcApplication3.Models.Feedback>" %>

<script type="text/javascript">

$(document).ready(
function() {

$(
'#feedbackForm').submit(function() {

var action = $('#feedbackForm').attr('action');

$.post(action,
$(
'#feedbackForm').serialize(),
callBack);

return false;

});
// end of form submit function

});
//end of document ready function

// call back function to load in the thanks view
function callBack(e) {
$(
'#feedbackformDiv').load('/Feedback/Thanks');
}

// close the feedback and reverse the displayfeedback bool value
function closeFeedback() {
$(
'#feedbackformDiv').hide();

if (displayFeedback != undefined) {
displayFeedback
= !displayFeedback;
}
}

</script>

<div class="feedback-ui">
<div class="feedback-ui-header">
Please leave some feedback
<div class="feedback-ui-header-close">
<a href="javascript:closeFeedback();">x</a>
</div>
</div>
<div class="feedback-ui-panel">
<form action="<%= Url.Action("Submit", "Feedback") %>" id="feedbackForm" method="post">
<p>
<%= Html.LabelFor(model => model.Comment) %>
<%= Html.TextAreaFor(model => model.Comment, 5, 50, new { Width = "350px;", Height = "150px;" }) %>
<%= Html.ValidationMessageFor(model => model.Comment) %>
</p>
<p>
<input type="submit" value="Submit" />
</p>
</form>
</div>
</div>

This is all relatively straight forward, the main bit to notice is the  serialize method on the form jQuery object. This serializes the contents of the form to the action. I originally thought this may get in the way of the model binding with the mvc framework into the action, but it magically sorts itself out. Genius!

The other part of functionality is the call back function from the form submit action. Very simply, this just loads in the next partial view to thank the user for submitting their comment to the team.



FeedbackThanks.aspx



The thanks form is very simple. Just a way of displaying a thank you note to your users and then disappear again leaving the user where they were, as if it never happened.



3



All the client side javascript does is after a defined time hit the thanks form you see above and invert the boolean variable to make the show/hide work correctly.




<script type="text/javascript">

$(document).ready(
function() {

setTimeout(
function() {
$(
'#feedbackformDiv').hide();
if (displayFeedback != undefined) {
displayFeedback
= !displayFeedback;
}
},
2000);

});

</script>

<div class="feedback-ui-thanks">
<div class="feedback-ui-header">
Thanks
</div>
<div class="feedback-ui-panel">
Thanks for helping improve our application.
</div>
</div>



And that’s it.



Summary



So what have we learnt from this little exercise:




  • Creation of views through the Visual Studio tooling for Asp.net MVC


  • jQuery basics – selectors, built in functions and basic dom manipulation


  • Partial view actions through controllers


  • Strongly typed model formatting



Additional extension points which would be good to plug in would be:




  • Additional fields / different types of feedback options


  • Response validation



What’s next?!



Well over the next couple of months I will be working on some interesting things and techniques and I hope to find time to share some of them with you all. If you have any suggestions of things to do, or any improvements then please leave a comment or get in contact through twitter.

No comments: