W jednym z poprzednich postów pokazałem jak zrealizować stronicowanie wyników. Kontrolka ListView z czystego ASP.NET miała jeszcze jedną zaletę – możliwość sortowania wyników po kolumnach. W dzisiejszym poście zaprezentuję sposób uzyskania takiego efektu w ASP.NET MVC w którym jak wiemy nie ma żadnych wbudowanych kontrolek.
Dla zwizualizowania, efekt końcowy pokazuję na powyższym screenie. Mamy kilka kolumn, które są linkami. Po kliknięciu na daną kolumnę chcemy aby wyniki były posortowane za pomocą tej kolumny. Po drugim kliknięciu w tą samą kolumnę kierunek sortowania zostaje odwrócony (z DESC na ASC i z ASC na DESC).
Zdecydowałem się na napisanie metody rozszerzającej HtmlHelper:
public static class HtmlHelper { public static string SortableColumn(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, string columnName, object routeValues) { return SortableColumn(htmlHelper, linkText, columnName, routeValues, "sord", "sidx",null); } public static string SortableColumn(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, string columnName, object routeValues, string sortTypeParName, string sortColumnParName) { return SortableColumn(htmlHelper, linkText, columnName, routeValues, sortTypeParName, sortColumnParName); } public static string SortableColumn(this System.Web.Mvc.HtmlHelper htmlHelper, string linkText, string columnName, object routeValues,string sortTypeParName,string sortColumnParName,string anchorName) { System.Web.Routing.RouteData data = htmlHelper.ViewContext.Controller.ControllerContext.RouteData; string actionName = data.GetRequiredString("action"); StringBuilder sb = new StringBuilder(); var vals = new RouteValueDictionary(routeValues); string sidx = String.Empty; if (System.Web.HttpContext.Current.Request[sortColumnParName] != null) { sidx = System.Web.HttpContext.Current.Request[sortColumnParName].ToString(); } //modify the sidx if (vals.ContainsKey(sortColumnParName) == false) { vals.Add(sortColumnParName, columnName); } else { vals[sortColumnParName] = columnName; } string sord = String.Empty; if (System.Web.HttpContext.Current.Request[sortTypeParName] != null) { sord = System.Web.HttpContext.Current.Request[sortTypeParName].ToString(); } //add the sord key if needed if (vals.ContainsKey(sortTypeParName) == false) { vals.Add(sortTypeParName, String.Empty); } //if column matches if (sidx.Equals(columnName, StringComparison.CurrentCultureIgnoreCase) == true) { if (sord.Equals("asc", StringComparison.CurrentCultureIgnoreCase) == true) { //draw the ascending sort indicator using the wingdings font. sb.Append(" <img src='/images/asc.png' alt='asc'/>"); vals[sortTypeParName] = "desc"; } else { sb.Append(" <img src='/images/desc.png' alt='desc'/>"); vals[sortTypeParName] = "asc"; } } else { vals[sortTypeParName] = "asc"; } if(anchorName==null) sb.Insert(0, System.Web.Mvc.Html.LinkExtensions.ActionLink(htmlHelper, linkText, actionName,vals)); else sb.Insert(0, System.Web.Mvc.Html.LinkExtensions.ActionLink(htmlHelper, linkText, actionName, null, null, null, anchorName, vals, null)); return sb.ToString(); } }
Powyższy kod jest przeróbką przykładu znalezionego na jednym z serwisów (nie pamiętam już niestety nazwy). Metoda przyjmuje kilka parametrów:
- object routeValues – tak samo jak w przypadku ActionLink (dodatkowe parametry do przekazania w URL).
- string sortTypeParName – nazwa parametru QueryString, który będzie przechowywać typ sortowania.
- string sortColumnParName – nazwa parametru QueryString, który będzie przechowywać nazwę kolumny po której należy sortować.
- string anchorName – dodatkowy parametr przechowujący nazwę kotwicy (np. #comments). Jeśli przekażemy null metoda zignoruje po prostu kotwicę.
Sposób wykorzystania metody jest zdecydowanie łatwiejszy. W widoku wystarczy:
<table class="tableList"> <tr> <th><%=Html.SortableColumn("Obietnica", "ModificationDate", null, "promisesSortType", "promisSortColumnName", "promises")%></th> <th><%=Html.SortableColumn("Źródło", "Source", null, "promisesSortType", "promisSortColumnName", "promises")%></th> </tr> ... </table>
W powyższym przykładzie ostatni parametr (promises) odpowiada za dołączenie kotwicy #promises. SortableColumn wygeneruje odpowiedni link z tekstem i obrazkiem symbolizującym kierunek sortowania (zielona strzałka na pierwszym screenie).
Pozostało jeszcze pokazać zawartość kontrolera, który odpowiada za faktyczne posortowanie danych:
public ActionResult Details(Guid id) { string promisesOrderString = (Request.QueryString["promisesSortColumnName"] ?? "ModificationDate") + " " + (Request.QueryString["promisesSortType"] ?? "asc"); PromisesRepository promisesRepository = new PromisesRepository(); IQueryable<Promises> allOrderedPromises= promisesRepository.GetAllOrderedBy()(promisesOrderString); ... return View(viewModel); }
Kluczem jest wygenerowania string’a w formacie ‘COLUMN_NAME ASC’ lub ‘COLUMN_NAME DESC’. W powyższym kodzie promisesOrderString zawiera właśnie taki string. Następnie używamy jednej z przeładowanych metod OrderBy i przekazujemy promisesOrderString. Należy zaznaczyć, że domyślnie LINQ nie zawiera metody OrderBy akceptującej jako parametr wejściowy zmienną typu string. Należy więc ją dodać jako rozszerzenie (kod klasy z CodePlex).
To jest w miare zrozumiale i oczywiste. Zastanawiam sie jak bys polaczyl sortowanie z filtrowaniem. Powiedzmy masz filtr nad gridem z lista pracownikow. W filtrze mozesz wpisac imie i nazwisko i po tym przefiltrowac liste, jest do tego guzik szukaj. Lista ma rozne kolumny (imie, nazwisko, pozycja, data zatrudnienia…) po ktorych mozesz sortowac. Powinno to dzialac tak, ze filtr masz aktualizowany tylko wtedy kiedy wcisniesz szukaj. I wtedy jak sortujesz kolumny, to sortujesz tylko te wyniki ktore otrzymales po odfiltrowaniu. Dodatkowo jak przeedytujesz cos w polach filtru ale nie wcisniesz wyszukaj to filtr sie nie zmienia. Szukam takich rozwiazan i jak na razie ciezko cos znalezc. Pozdrawiam