Monday, November 26, 2012

WebGrid: AJAX Updates, Server Sorting

This is a brief summary of the changes I did to implement the AJAX updates to the WebGrid and sorting behaviour. I plan to put more detailed notes and the source code on my website

To use AJAX and update grid content, firstly the grid needs to be placed in the div which has an id. The ajaxUpdateContainerId has to be specified in the WebGrid declaration. To put it simple, in my main view I have a div

<div id="wbgrid" style="float:left; min-width:500px;">
 @Html.Partial("_WebGrid", Model)
</div>

And in the partial view the div name wbgrid is specified as ajaxUpdateContainerId.

@{ var grid = new WebGrid<Recipes.Models.Yahoo.YahooData>(null, rowsPerPage: 5, defaultSort: "YahooSymbolName", ajaxUpdateContainerId: "wbgrid");
grid.Bind(Model.Datas, rowCount: Model.TotalRows, autoSortAndPage: false);
}

The link on the WebGrid column has the following format http://localhost/Yahoo/Index?sort=DateTime&sortdir=ASC

Therefore, the controller function can automatically receive those parameters with the following signature:

public ActionResult Index(int page = 1, string sort = "YahooSymbolName", string sortDir = "Ascending")

The parameters will be then passed over to the function that retrieves data

public List<YahooData> GetData(out int totalRecords, int pageSize, int pageIndex, string sort = "YahooSymbolName", SortDirection sortOrder = SortDirection.Ascending )
{
 IQueryable<YahooData> data = db.YahooData;
 totalRecords = data.Count();

 Func<IQueryable<YahooData>, bool, IOrderedQueryable<YahooData>> applyOrdering = _dataOrderings[sort];
 data = applyOrdering(data, sortOrder == SortDirection.Ascending);

 List<YahooData> result = data.ToList();
 if(pageSize > 0 && pageIndex >=0)
 {
  result = result.Skip(pageIndex*pageSize).Take(pageSize).ToList();
 }
 return result;
}

A couple of helper functions are utilized by GetData: CreateOrderingFunc and _dataOrderings

// helpers that take an IQueryable<Product> and a bool to indicate ascending/descending
// and apply that ordering to the IQueryable and return the result
private readonly IDictionary<string, Func<IQueryable<YahooData>, bool, IOrderedQueryable<YahooData>>>
 _dataOrderings = new Dictionary<string, Func<IQueryable<YahooData>, bool, IOrderedQueryable<YahooData>>>
       {
        {"YahooSymbolName", CreateOrderingFunc<YahooData, string>(p=>p.DataName)},
        {"Ask", CreateOrderingFunc<YahooData, decimal?>(p=>p.Ask)},
        {"Time", CreateOrderingFunc<YahooData, DateTime>(p=>p.DateTime)}
        // Add for more columns ...
       };

/// returns a Func that takes an IQueryable and a bool, and sorts the IQueryable (ascending or descending based on the bool).
/// The sort is performed on the property identified by the key selector.
private static Func<IQueryable<T>, bool, IOrderedQueryable<T>> CreateOrderingFunc<T, TKey>(Expression<Func<T, TKey>> keySelector)
{
 return
  (source, ascending) => ascending ? source.OrderBy(keySelector) : source.OrderByDescending(keySelector);
}

Finally, to complete the functional example, I added a jQuery dialog that displays the data that was retrieved from Yahoo. In the view, the RetrieveData function triggers the controller action AddDataToDB (which calls the Yahoo website and adds results to the database).

function RetrieveData() {
 $.post('@Url.Action("AddDataToDB","yahoo")',
 function (d) {
  ShowDialog(d.o);
 });
}

function ShowDialog(msg) {
 $('<div/>').dialog({ title: 'Retrieved the following data', width: 450, height: 250, close: function(){ location.reload(); }}).html(msg);
}

AddDataToDB returns a Json result, containing the html table.

public ActionResult AddDataToDB()
{
 List<YahooData> datas = GetSingleSet();
 datas.ForEach(d => db.YahooData.Add(d));
 db.SaveChanges();

 string s = "<table><thead><tr class=\"webgrid-header\"><th>Company</th><th>Time</th><th>LTP</th><th>Volume</th><th>Ask</th><th>Bid</th><th>High</th><th>Low</th></tr></thead><tbody>";

 foreach (var yahooData in datas)
 {
  s = s + "<tr class=\"webgrid-row-style\">" + 
   "<td class=\"company\">" + yahooData.DataName + "</td>" +
   "<td class=\"time\">" + yahooData.DateTime.ToString("dd/MM/yyyy hh:mm") + "</td>" +
   "<td class=\"ask\">" + yahooData.LTP + "</td>" +
   "<td class=\"volume\">" + yahooData.Volume + "</td>" +
   "<td class=\"ask\">" + yahooData.Ask + "</td>" +
   "<td class=\"ask\">" + yahooData.Bid + "</td>" +
   "<td class=\"ask\">" + yahooData.High + "</td>" +
   "<td class=\"ask\">" + yahooData.Low + "</td>" +
   "</tr>";
 }

 s = s + "</tbody></table>";

 return Json(new { o = s });
}

The result is then utilised by the ShowDialog function, that displays a jQuery dialog. When the user closes the dialog, the page is refreshed so that the WebGrid contents are updated with the latest data retrieved.

Complete example

References

Get the Most out of WebGrid in ASP.NET MVC
by . Also posted on my website

No comments: