Tuesday, April 22, 2014

MVC 4 WebGrid – Filtering on Selected Columns with DropDownLists in Header

This example demonstrates a MVC 4 WebGrid implementation to display subsets of data filtered using drop-down lists embedded in grid column headers. Example grid has two columns 'Code' and 'Status'.  A filter dropdown list is added in the 'Status' column header, which contains all the unique values within the column. This filter dropdown list is populated using ViewBag and filtering is happening on its item change postback. A postback is forced on onchange event of the drop-down list using jQuery.

WebGrid control header doesn't support inserting HTML directly. Therefore this implementation uses MvcHtmlString class and simple string replace to insert a dropdown control to the header. I use a placeholder called '{StatusFilter}' in the header initially and then replace that with the HTML markup for the dropdown list.

View:

@model hub.Models.MyCampModel

@{
  if (Model != null && Model.MyCampaigns != null 
          && Model.MyCampaigns.Count() > 0)
  {
   using (Html.BeginForm())
   {       
    var grid = new WebGrid();
    grid.Bind(Model.MyCampaigns, rowCount: Model.MyCampaigns.Count());
    grid.Pager(WebGridPagerModes.All);
   
    @MvcHtmlString.Create(grid.GetHtml(
     tableStyle: "main_table_wrapper",
      alternatingRowStyle: "descending",
     rowStyle: "ascending",
     columns: grid.Columns(
      grid.Column("StatusCode", header: "Code"),
      grid.Column("Status", header: "Status {StatusFilter}"))  
    ).ToString().Replace("{StatusFilter}", Html.DropDownList("RequestStatusFilter", ViewBag.ReqStatusOptions as SelectList, "All", new { onchange = "$(this).closest('form').submit();" }).ToString()));
   }   
  } 
}


Post action to get filtered results:

[HttpPost]
public ActionResult CampaignList(string requestStatusFilter = "")
{
  MyCampModel model = new MyCampModel();
  model.MyCampaigns = new List<MyCampaign>();
  var myRecs = db.MyCampaigns.Where(itm => (string.IsNullOrEmpty(requestStatusFilter) || itm.MyCampaignStatus.Name == requestStatusFilter));
  foreach (var rec in myRecs)
  {
    MyCampaign myCamp = new MyCampaign() { StatusCode = rec.Code, Status = rec.MyCampaignStatus.Name };
    model.MyCampaigns.Add(myCamp);
  }
  return View(model);
  ViewBag.ReqStatusOptions = new SelectList( // logic to select statuses.
}

Here the drop-down list is rendered in addition to the header text. Therefore sorting by column headers is also available in the same time.

Wednesday, April 16, 2014

SharePoint 2013 User Welcome Control with Client-Side Scripting

This is an example of using SharePoint 2013 JSOM to get the current logged in user's account information. This example uses SP.UserProfiles.PeopleManager object comes in SP.UserProfiles.js file and greets the currently logged on user saying “Hi, first name, last name [picture]”.

In order to load the profile picture, I used userphoto.aspx page resides in the SharePoint layouts folder. Two parameters passed for that are the picture size and account name (user email address in SharePoint online).

This code looks for a DIV element with the id "userDetails" on the page and will place the generated markup in it.

var TestApp = TestApp || {};

TestApp.UserWelcome = function (TestApp) {
 var context;
 var user;
 var personProperties;

 var getCurrentUser = function () {
  context = SP.ClientContext.get_current();
  user = context.get_web().get_currentUser();
  context.load(user);
  context.executeQueryAsync(getUserProperties, onGetUserFailed);
 };

 var onGetUserFailed = function (sender, args) {
  if ($('#userDetails').length) {
   $get("userDetails").innerHTML = "Get current user failed: " + args.get_message();
  }
 };

 var getUserProperties = function () {
  var targetUser = user.get_loginName();
  var peopleManager = new SP.UserProfiles.PeopleManager(context);
  personProperties = peopleManager.getPropertiesFor(targetUser);
  context.load(personProperties);
  context.executeQueryAsync(onUserPropSuccess, onUserPropFail);
 };

 var onUserPropSuccess = function () {
  var userPhoto = "/_layouts/15/userphoto.aspx?size=s&accountname=" + currentUser.get_email();
  var displayName = personProperties.get_displayName();
  displayName = displayName.replace(/\(.*\)/g, '');

  var detailHtml = '<div>Hi, ' + displayName + '<img src="' + userPhoto + '" /></div>';

  if ($('#userDetails').length) {
   $get("userDetails").innerHTML = detailHtml;
  }
 };

 var onUserPropFail = function (sender, args) {
  if ($('#userDetails').length) {
   $get("userDetails").innerHTML = "Error: " + args.get_message();
  }
 };

 return {
  getCurrentUser: getCurrentUser
 };
}();

$(document).ready(function () {
 var initWelcomeUser = function () {
  TestApp.UserWelcome.getCurrentUser();
 };

 ExecuteOrDelayUntilScriptLoaded(initWelcomeUser, 'SP.UserProfiles.js');
});

Additionally, namespaces are used in this example in order to reduce the number of objects and functions that are added to the global scope of the application.

Wednesday, April 9, 2014

Wait Until Loop of jQuery AJAX Requests are Done

I have an application that requires a set of search results to be flagged if the user selects a checkbox in front of the each search result item. I use an each() loop to set the flag of each search result item via an AJAX call. Then there is another requirement to show all flagged items in a different panel. So I wanted to run a function after above jQuery .each() is done. That is after finishing all the Ajax requests in the each() loop.


jQuery.when() can be used in this kind of scenario. It takes a variable number of arguments and makes it possible to call a callback when every argument passed to it is resolved. If the number of Ajax calls are fixed, the syntax will be:

$.when(ajax1(), ajax2(), ajax3(), ajax4()).done(function(a1, a2, a3, a4){
   // code to be executed after four ajax requests complete.
});

But in my case, since the number of selection is not the same always, approach to be used is bit different. Actually I have to pass an array of arguments (deferred objects). So we can use .apply, which makes it possible to call a function with an array of arguments.  

For that I declared an array for deferred objects. Then pass it to the function where each Ajax call is happening.

// Declare an array of Deferred objects.
var searchRsltDeferreds = [];
$('#searchResultsDiv input').each(function () {
  if ($(this).is(':checked')) {
    // Follow selected documents.
    followDocument($(this).val(), searchRsltDeferreds);
  }
  else {
    // Stop following not selected documents.
    stopFollowDocument($(this).val(), searchRsltDeferreds);
  }
});

Then in the followDocument() function (where Ajax call is), I push the request object to the deferred objects array.

// Make the current user start following a document.
var followDocument = function (documentUrl, deferredsArray) {
  var followRequest = $.ajax({
    url: _spPageContextInfo.siteAbsoluteUrl + "/_api/social.following/follow",
    type: "POST",
    data: JSON.stringify({
    "actor": {
      "__metadata": {
        "type": "SP.Social.SocialActorInfo"
      },
      "ActorType": 1,
      "ContentUri": documentUrl,
      "Id": null
      }
    }),
    headers: {
    "accept": "application/json;odata=verbose",
    "content-type": "application/json;odata=verbose",
    "X-RequestDigest": $("#__REQUESTDIGEST").val()
    },
    success: function (responseData) {
      //          
    },
    error: followRequestFailed
  });
  deferredsArray.push(followRequest);
};

When all the objects in the deferred array are resolved, call getMyFollowedDocuments() function which will refresh all flagged items tab.

$.when.apply(null, searchRsltDeferreds).done(function () {
  // Refresh my flagged items tab.
  getMyFollowedDocuments();
});