Glance at jEditable in ASP.NET MVC

Recently one of my colleagues suggested that I should take a look at Jeditable plugin. This plugin allows in place edition of XHTML elements content. I can imagine few interesting scenarios with this plugin, so I decide to put together a simple sample.
Let's start with some markup, which will represent an NorthWind employee details (you can download NorthWind database from here):
TitleOfCourtesy: <u><%: Model.TitleOfCourtesy %></u><br />
FirstName: <u><%: Model.FirstName %></u><br />
LastName: <u><%: Model.LastName %></u><br />
Title: <b class="edit" id="Title"><%: Model.Title %></b><br />
Address: <b class="edit" id="Address"><%: Model.Address %></b><br />
PostalCode: <b class="edit" id="PostalCode"><%: Model.PostalCode %></b><br />
City: <b class="edit" id="City"><%: Model.City %></b><br />
Country: <b class="edit-select" id="Country"><%: Model.Country %></b><br />
Notes: <i class="edit-area" id="Notes"><%: Model.Notes %></i><br />
<br /><br /><button id="Submit">Submit</button>
I would like to keep this sample a simple as possible, so there will be no fancy design or architecture:
  • fields which will be edited in standard text input are wrapped in <b> element with class 'edit'
  • fields which will be edited in select are wrapped in <b> element with class 'edit-select'
  • fields which will be edited in textarea are wrapped in <i> element with class 'edit-area'
After making that clear, we can add Jeditable functionality to those elements. First we should reference two scripts:
<script src="<%= Url.Content("~/Scripts/jquery-1.4.1.min.js") %>" type="text/javascript"></script>
<script src="<%= Url.Content("~/Scripts/jquery.jeditable.mini.js") %>" type="text/javascript"></script>
Now we can write some JavaScript:
$(document).ready(function () {
//Standard 'text' inputs
$('.edit').editable($.updateEmployee, {
//Element tooltip
tooltip: 'Click to edit...',
//Input style
style: 'display: inline'
});
//Selects
$('.edit-select').editable($.updateEmployee, {
//Options for select (you can use loadurl if you want to request data from server)
data: "{'USA':'USA','UK':'UK'}",
//Type
type: 'select',
//Text for accept button
submit: 'Accept',
//Element tooltip
tooltip: 'Click to edit...',
//Select style
style: 'display: inline'
});
//TextAreas
$('.edit-area').editable($.updateEmployee, {
//Type
type: 'textarea',
//Text for cancel button
cancel: 'Cancel',
//Text for accept button
submit: 'Accept',
//Element tooltip
tooltip: 'Click to edit...',
//TextArea style
style: 'display: inline'
});
});
The first parameter of editable is usually an URL where browser posts edited content. In that situation, every field is being posted as soon as user finishes editing. There is also a possibility of passing function in first parameter. As you can see, I'm using second approach here. The $.updateEmployee function looks like this:
$.updateEmployee = function (value, settings) {
//Based on the current element, we update the corresponding property of employee
switch ($(this).attr('id')) {
case 'Title':
window.Employee.Title = value;
break;
case 'Address':
window.Employee.Address = value;
break;
case 'PostalCode':
window.Employee.PostalCode = value;
break;
case 'City':
window.Employee.City = value;
break;
case 'Country':
window.Employee.Country = value;
break;
case 'Notes':
window.Employee.Notes = value;
break;
}
//We have to return string, it will be put into element for displaying
return (value);
}
You may wonder where the window.Employee object came from. It's being initialized like this (I know it's ugly, but it's only for sample purposes):
(function ($) {
window.Employee = {
EmployeeID: '<%: Model.EmployeeID %>',
Title: '<%: Model.Title %>',
Address: '<%: Model.Address %>',
PostalCode: '<%: Model.PostalCode %>',
City: '<%: Model.City %>',
Country: '<%: Model.Country %>',
Notes: '<%: Model.Notes %>'
};
})(jQuery);
This object allows me to perform a single POST for all fields, when user clicks Submit button:
$('#Submit').click(function () {
$.ajax({
type: 'POST',
contentType: 'application/json; charset=utf-8',
url: '<%=Url.Action("Details", "Home") %>',
dataType: 'json',
data: $.toJSON(window.Employee)
});
});
You can go ahead and click around on our editable elements:
Complete sample code can be downloaded here, enjoy.

ASP.NET 3.5, ToolkitScriptManager 3.5.40412 and Sys.Services.AuthenticationService

Today I have updated AjaxControlToolkit in one of my ASP.NET 3.5 projects to version 3.5.40412 (there are few important bug fixes for me in this version). If someone would just replace the reference to AjaxControlToolkit.dll (like I did) he would see following error message:

AjaxControlToolkit requires ASP.NET Ajax 4.0 scripts. Ensure the correct version of the scripts are referenced. If you are using an ASP.NET ScriptManager, switch to the ToolkitScriptManager in AjaxControlToolkit.dll.

The message is pretty clear, so I replaced ScriptManager with ToolkitScriptManager:
<AjaxToolkit:ToolkitScriptManager ID="smContent" EnablePageMethods="true" EnableScriptGlobalization="true" EnableScriptLocalization="true" runat="server">
</AjaxToolkit:ToolkitScriptManager>

After that change everything seemed to work just fine, until I decided to log in. It happens that this project is using Sys.Services.AuthenticationService for login and logout operations. Any attempt to login resulted in 'object doesn't support this property or method' message. So something must be wrong with JavaScript's emitted by manager. I made a quick comparison of scripts referenced by ScriptManager and ToolkitScriptManager. Here are the headers of interesting one:
// Name:        MicrosoftAjax.js
// Assembly: System.Web.Extensions
// Version: 3.5.0.0
// FileVersion: 3.5.30729.196


// Name: MicrosoftAjax.js
// Assembly: AjaxControlToolkit
// Version: 3.5.40412.0
// FileVersion: 3.5.40412.2

The MicrosoftAjax.js referenced by ToolkitScriptManager has a lot less content than the one referenced by ScriptManager. Judging by the first error message, this script should be the one from ASP.NET AJAX 4.0 scripts. If that's the case, lets take a look at Microsoft AJAX CDN for ASP.NET AJAX 3.5 and ASP.NET AJAX 4.0. As you can see, in ASP.NET AJAX 4.0 Microsoft has split MicrosoftAjax.js in a lot of smaller files. The one we need is MicrosoftAjaxApplicationServices.js, let's download it and add to ToolkitScriptManager scripts:
<AjaxToolkit:ToolkitScriptManager ID="smContent" EnablePageMethods="true" EnableScriptGlobalization="true" EnableScriptLocalization="true" runat="server">
<Scripts>
<asp:ScriptReference Path="~/Scripts/MicrosoftAjaxApplicationServices.js" />
</Scripts>
</AjaxToolkit:ToolkitScriptManager>

Unfortunately this still doesn't work. The reason is that inline script, which sets default path for service, is executed before the script we have just added. We need to set the path for the service ourselves:
<script type="text/javascript">
Sys.Application.add_init(function() { Sys.Services.AuthenticationService.set_path('<%= ResolveClientUrl("~/Authentication_JSON_AppService.axd") %>'); });
</script>

Now it will work. I hope that this will be helpful for people who will encounter similar problem.

Partial forms validation in ASP.NET MVC 2 (something like WebForms ValidationGroup)

In one of my recent ASP.NET MVC 2 projects I had to make a form divided into few tabs. Each tab had to be validated before user can move to next one. Unfortunately ASP.NET MVC 2 built in validation doesn't provide mechanism for this. Well we are developers, so if there is no built in mechanism, we should create our own. First lets create a ValidationAttribute, which will be used to add information about validation group:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class ValidationGroupAttribute : ValidationAttribute
{
  #region Properties
  /// <summary>
  /// Name of the validation group
  /// </summary>
  public string GroupName { get; set; }
  #endregion

  #region Constructor
  public ValidationGroupAttribute(string groupName)
  {
    GroupName = groupName;
  }
  #endregion

  #region Methods
  public override bool IsValid(object value)
  {
    //No validation logic, always return true
    return true;
  }
  #endregion
}

We will also need a DataAnnotationsModelValidator for this attribute:
public class ValidationGroupValidator : DataAnnotationsModelValidator<ValidationGroupAttribute>
{
  #region Fields
  string _groupName;
  #endregion

  #region Constructor
  public ValidationGroupValidator(ModelMetadata metadata, ControllerContext context, ValidationGroupAttribute attribute)
: base(metadata, context, attribute) 
  {
    _groupName = attribute.GroupName;
  }
  #endregion

  #region Methods
  public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
  {
    //Add informations about validation group to metadata
    var validatioRule = new ModelClientValidationRule
    {
      ErrorMessage = String.Empty,
      ValidationType = "validationGroup"
    };
    validatioRule.ValidationParameters.Add("groupName", _groupName);

    return new[] { validatioRule };
  }
  #endregion
}

Don't forget to register above classes in your Global.asax:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(ValidationGroupAttribute), typeof(ValidationGroupValidator));

So this is all we need on server side (please remember, that we don't want to achieve any server side logic, only partial validation on client side before posting the whole form). To make it work on client side, first we need to modify Sys.Mvc.FormContext._parseJsonOptions in MicrosoftMvcValidation.debug.js (or corresponding part in MicrosoftMvcValidation.js):
Sys.Mvc.FormContext._parseJsonOptions = function Sys_Mvc_FormContext$_parseJsonOptions(options) {
  var formElement = $get(options.FormId);
  var validationSummaryElement = (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(options.ValidationSummaryId)) ? $get(options.ValidationSummaryId) : null;
  var formContext = new Sys.Mvc.FormContext(formElement, validationSummaryElement);
  formContext.enableDynamicValidation();
  formContext.replaceValidationSummary = options.ReplaceValidationSummary;
  for (var i = 0; i < options.Fields.length; i++) {
    var field = options.Fields[i];
    var fieldElements = Sys.Mvc.FormContext._getFormElementsWithName(formElement, field.FieldName);
    var validationMessageElement = (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(field.ValidationMessageId)) ? $get(field.ValidationMessageId) : null;
    var fieldContext = new Sys.Mvc.FieldContext(formContext);
    Array.addRange(fieldContext.elements, fieldElements);
    fieldContext.validationMessageElement = validationMessageElement;
    fieldContext.replaceValidationMessageContents = field.ReplaceValidationMessageContents;
    for (var j = 0; j < field.ValidationRules.length; j++) {
      var rule = field.ValidationRules[j];
      //Here goes our small modification
      if (rule.ValidationType == 'validationGroup') {
        fieldContext.validationGroup = rule.ValidationParameters['groupName'];
      }
      else {
        var validator = Sys.Mvc.ValidatorRegistry.getValidator(rule);
        if (validator) {
          var validation = Sys.Mvc.$create_Validation();
          validation.fieldErrorMessage = rule.ErrorMessage;
          validation.validator = validator;
          Array.add(fieldContext.validations, validation);
        }
      }
    }
    fieldContext.enableDynamicValidation();
    Array.add(formContext.fields, fieldContext);
  }
  var registeredValidatorCallbacks = formElement.validationCallbacks;
  if (!registeredValidatorCallbacks) {
    registeredValidatorCallbacks = [];
    formElement.validationCallbacks = registeredValidatorCallbacks;
  }
  registeredValidatorCallbacks.push(Function.createDelegate(null, function () {
    return Sys.Mvc._validationUtil.arrayIsNullOrEmpty(formContext.validate('submit'));
  }));
  return formContext;
}

Now we can write ourselves function, which will perform partial validation based on group name:
Sys.Mvc.FormContext.validateGroup = function Sys_Mvc_FormContext$validateGroup(formId, groupName) {
  //Get form element
  var formElement = $get(formId);
  //Get form context
  var formContext = Sys.Mvc.FormContext.getValidationForForm(formElement);
  //Get form fields
  var fields = formContext.fields;
  //Array for errors
  var errors = [];
  //For each field
  for (var i = 0; i < fields.length; i++) {
    var field = fields[i];
    //If field has validation group and its name matches the one we are looking for
    if (field.validationGroup && field.validationGroup == groupName) {
      //Validate field
      var fieldErrors = field.validate('submit');
      if (fieldErrors) {
        Array.addRange(errors, fieldErrors);
      }
    }
  }
  //Return true it there are no errors, otherwise false
  return (!errors || !errors.length);
}

And our job is done. Now we can call Sys.Mvc.FormContext.validateGroup('formId', 'validationGroup'); whenever we want to perform partial validation. I have created a sample application which make use of those modification, you can download it from my repository. If there is a need for same modification for jQuery validation, let me know and I will look into it.