Auch außerhalb von SharePoint konzipiert und implementiert die HanseVision innovative und zielführende Applikationen. Derzeit bin ich in einem äußerst interessanten Projekt involviert, bei dem die Ablösung einer Access-Anwendung im Fokus steht. Die neue Applikation wird im Webumfeld realisiert und stellt mein Projektteam laufend vor neue, spannende Herausforderungen.

Da auch die Pflege der Stammdaten über das Web-Interface realisiert werden soll und die Fachanwender Jahre-lang mit der Access-Oberfläche gearbeitet haben, muss die neue Anwendung den Ansprüchen der Fachanwender gerecht werden. D. h.: Es darf keine Abstriche beim Bedienkomfort geben. Hierzu gehört auf jeden Fall eine intuitive und komfortable Bedienung bei der Auflistung von Datensätzen.

Die Umsetzung findet in ASP.Net statt. Als zusätzliche Innovation wird auf ASPX MVC gesetzt, um einen maximal, modularen Aufbau des Frontends zu erreichen. ASPX MVC erleichtert durch die automatische Generierung von Sichten (anhand der Model-Informationen) das Arbeiten immens. Bei einer Auflistung von Datensätzen in der sogenannten Index-Ansicht ist der Bedienkomfort – für unsere Anforderungen –  allerdings nicht ausreichend, da die Daten out-of-the-box in einer reinen HTML-Tabelle gerendert werden. Da die Anwender die Datensätze später durchsuchen, filtern und sortieren möchten, muss an dieser Stelle also ein Lösungsansatz her. Aus Erfahrung weiß ich allerdings, dass das Umzusetzen dieser Funktionalität zeitraubend, aufwendig und somit teuer für dem Kunden ist.

Bei meiner Recherche nach einem passenden Lösungsansatz für das Problem,bin ich auf ein freies Control Set gestoßen, das mich umgehend überzeugt hat. Die Lösung heißt “DataTables” und ist unter http://datatables.net/ erhältlich. Im Grunde genommen handelt es sich um eine jQuery-Bibliothek, die sämtliche Operationen auf HTML-Tabellen unterstützt.

Nun ist es aber so, dass in unserer Anwendung sehr viele Datensätze verwaltet werden und wir diese nicht in Gänze in die Oberfläche laden können, da sonst Tabellen mit mehr als 50 000 Zeilen vom Browser verarbeitet werden müssten. Dies wäre Performanz-technisch eine Katastrophe. Zum Glück ist DataTables auch in der lange Server-seitig zu arbeiten, so dass Paging, Sortieren und Filtern auf dem Server stattfinden kann. Die Herausforderung lang nun darin dies mit unserer MVC Applikation zu verheiraten. Die Lösung habe ich nachfolgend für die Verwaltung von Räumen kurz skizziert.

 

Index-View

In der Index-View wird nun anstatt der vom Scaffolding (Generator) generierten HTML-Tabelle das Code-Konstrukt von DataTables eingebunden. In unserm Projekt haben wir den erforderlichen jQuery- und HTML Code in einen zentralen HTML-Helper ausgelagert, damit Änderungen nicht auf jeder Seite vorgenommen werden müssen.

<div class="TablePart">
    @Html.TableFor(New RaumEntity(), New DataTableConfiguration() With _
                      { _
                          .ScrollY = 300, _
                          .SelectionMethod = RowSelectionType.NoSelect, _
                          .Controller = "Raum", _
                          .Action = "AjaxHandler", _
                          .ServerSide = True, _
                          .TableName = "Raumable", _
                          .PaginationType = PaginationType.FullNumbers, _
                          .ServerDataFunction = "fnServerObjectToArray", _
                          .Columns = New DataTableColumnConfiguration() _
                        { _
                            New DataTableColumnConfiguration() With _
                            { _
                                .HeaderText = "", _
                                .Searchable = True,
                                .PropertyName = "Id", _
                                .ColumnSize = 50, _
                                .TemplateFunc = String.Format(
                                    "'<a class=""actionLinkUI ui-widget ui-state-default ui-corner-all ui-icon ui-icon-pencil"" href=""{0}/' + value + '"" /><a class=""actionLinkUI ui-widget ui-state-default ui-corner-all ui-icon ui-icon-note"" href=""{1}/' + value + '"" />'", Url.Action("Edit"), Url.Action("Details")) _
                            }, _
                            New DataTableColumnConfiguration() With _
                            { _
                                .PropertyName = "LFDNR", _
                                .Searchable = True, _
                                .Sortable = True, _
                                .FilterType = FilterType.TextFilter,
                                .ColumnSize = 100 _
                            }, _
                            New DataTableColumnConfiguration() With _
                            { _
                                .PropertyName = "GebaeudeEntity.Bezeichnung", _
                                .HeaderText = "Gebäudebezeichnung",
                                .Searchable = True,
                                .Sortable = True,
                                .FilterType = FilterType.TextFilter,
                                .ColumnSize = 200 _
                            }, _
                            New DataTableColumnConfiguration() With _
                            { _
                                .Searchable = True,
                                .Sortable = True,
                                .PropertyName = "Stockwerk", _
                                .FilterType = FilterType.TextFilter _
                            }, _
                            New DataTableColumnConfiguration() With _
                            { _
                                .Searchable = True, _
                                .Sortable = True,
                                .PropertyName = "RaumartkombinationEntity.FachEntity.Name", _
                                .HeaderText = "Fach",
                                .FilterType = FilterType.TextFilter _
                            },
                            New DataTableColumnConfiguration() With _
                            { _
                                .Searchable = True, _
                                .Sortable = True,
                                .PropertyName = "Groesse", _
                                .HeaderText = "Größe m²",
                                .FilterType = FilterType.NumberRangeFilter
                            },
                            New DataTableColumnConfiguration() With _
                            { _
                                .Searchable = True, _
                                .Sortable = True,
                                .PropertyName = "FlaechenbezeichnungEntity.Bezeichnung", _
                                .HeaderText = "Flächenbezeichnung",
                                .FilterType = FilterType.TextFilter _
                            } _
                        }.ToList() _
                      })
</div>

In der gerenderten HTML-Seite sieht der jQuery- und HTML-Code nun wie folgt aus. Sowohl der Tabellen-Rumpf als auch der Script-Teil sind dabei erforderlich.

<div class=“TablePart“>
    <div class=“TableContainer_All“>
    <div class=“table-no-buttons TableContainer“>
        <table id=“Raumable“>
            <thead>
                <tr>
                    <td>Id</td>
                    <td>LFDNR</td>
                    <td>Gebäudebezeichnung</td>
                    <td>Stockwerk</td>
                    <td>Größe m²</td>
                </tr>
            </thead>
            <tbody></tbody>
            <tfoot>
                <tr>
                    <td>
                        <input class=“searchBox searchBox-Raumable“ id=“0″ name=“s_Id“ tableRef=“Raumable“ type=“text“ />
                    </td>
                    <td>
                        <input class=“searchBox searchBox-Raumable“ id=“1″ name=“s_LFDNR“ tableRef=“Raumable“ type=“text“ />
                    </td>
                    <td>
                        <input class=“searchBox searchBox-Raumable“ id=“2″ name=“s_GebaeudeEntity.Bezeichnung“ tableRef=“Raumable“ type=“text“ />
                    </td>
                    <td>
                        <input class=“searchBox searchBox-Raumable“ id=“3″ name=“s_Stockwerk“ tableRef=“Raumable“ type=“text“ />
                    </td>
                    <td>
                        <input class=“searchBox searchBox-Raumable“ id=“4″ name=“s_Groesse“ tableRef=“Raumable“ type=“text“ />
                    </td>
                </tr>
            </tfoot>
        </table>
    </div>
</div> <script type=“text/javascript“>
var asInitVals = new Array();
$(document).ready(function(){
 
var oTable = $(‚#Raumable‘).dataTable({
    „sAjaxSource“: „/Raum/AjaxHandler“,
    „bAutoWidth“: false,
    „bScrollAutoCss“: true,
    „aoColumns“ : [
        {
            „sWidth“: „50“,
            „sFilterType“: „text“,
            „sName“: „Id“,
            „bSearchable“: true,
            „bSortable“: false,
            „fnRender“: function(oObj)  { var columnIndex=0; var value=oObj.aData[columnIndex]; return ‚<a class=“actionLinkUI ui-widget ui-state-default ui-corner-all ui-icon ui-icon-pencil“ href=“/Raum/Edit/‘ + value + ‚“ /><a class=“actionLinkUI ui-widget ui-state-default ui-corner-all ui-icon ui-icon-note“ href=“/Raum/Details/‘ + value + ‚“ />‘; } ,
            „bVisible“: true
        },
        {
            „sWidth“: „100“,
            „sFilterType“: „text“,
            „sName“: „LFDNR“,
            „bSearchable“: true,
            „bSortable“: true,
            „fnRender“: function(oObj)  { var columnIndex=1; var value=oObj.aData[columnIndex]; return value; } ,
            „bVisible“: true
        },
        {
            „sWidth“: „200“,
            „sFilterType“: „text“,
            „sName“: „GebaeudeEntity.Bezeichnung“,
            „bSearchable“: true,
            „bSortable“: true,
            „fnRender“: function(oObj)  { var columnIndex=2; var value=oObj.aData[columnIndex]; return value; } ,
            „bVisible“: true
        },
        {
            „sWidth“: „80px“,
            „sFilterType“: „text“,
            „sName“: „Stockwerk“,
            „bSearchable“: true,
            „bSortable“: true,
            „fnRender“: function(oObj)  { var columnIndex=3; var value=oObj.aData[columnIndex]; return value; } ,
            „bVisible“: true
        },
        {
            „sWidth“: „80px“,
            „sFilterType“: „number-range“,
            „sName“: „Groesse“,
            „bSearchable“: true,
            „bSortable“: true,
            „fnRender“: function(oObj)  { var columnIndex=4; var value=oObj.aData[columnIndex]; return value; } ,
            „bVisible“: true
        }
    ],
    „bScrollInfinite“: false,
    „oLanguage“:
    {
        „sUrl“: „/Content/de-DE.js“
    },
    „sPaginationType“: „full_numbers“,
    „bStateSave“: false,
    „fnServerData“: fnServerObjectToArray,
    „bServerSide“: true,
    „sScrollY“: 300,
    „bJQueryUI“: true,
    „bProcessing“: true
}); registeredDataTables.push(oTable); fnInitializeTableButtons(‚Raumable‘);
$(‚tfoot input‘).keyup(function () { var table = $(‚#‘ + this.attributes[‚tableRef‘].value).dataTable(); table.fnFilter(this.value, this.id); });

 
});

</script>
</div>

 

In der Benutzeroberfläche werden die Raum-Datensätze inklusive Paging, Filterung und Sortierung dargestellt. (Durch den Einsatz von jQuery fühlt sich die Handhabung nicht an, als wäre man gerade auf einer Webseite!)

image

 

Die Index-Ansicht braucht nun tatsächlich keine Liste von View Models übergeben zu bekommen, da DataTables sich die Datensätze selbst asynchron abholt. Dazu müssen lediglich der Controller und die Action im DataTables-Code  angegeben werden. Wie so eine Aktion im Controller dann aussehen muss, ist nachfolgen beispielhaft dargestellt.

Controller

Es ist erforderlich, dass die Aktion im Controller einen Rückgabewert vom Type ActionResult hat. Aus der Datenbank werden immer alle Räume als IQueryable geholt. IQueryable ist in sofern wichtig, da an dieser Stelle noch keine Abfrage gegen die Datenbank ausgeführt wird. Erst beim Aufruf der Methode ToList() nach sämtlichen Paging-, Filter- und Sortieroperationen (in der Methode GetJson) wird die Abfrage an die Datenbank geschickt.

Public Class RaumController
        Inherits BaseController(Of RaumEntity)

        
        ' GET: /Raum/
        <OutputCache(duration:=0, varybyparam:="*")>
        Public Overrides Function AjaxHandler(params As JQueryDataTableParamModel) As ActionResult

            Dim entities As IQueryable(Of RaumEntity) = StammdatenDatenManager.LadeRaeume(True)


            Return GetJson(params,
                           Function(r) New String() {
                                Convert.ToString(r.Id),
                                If(r.LfdNr, String.Empty),
                                If(r.AufmassGesamt, String.Empty),
                                If(r.AufmassLaenge, String.Empty),
                                If(r.AufmassBreite, String.Empty),
                                If(r.IstMobil, String.Empty),
                                If(r.BestandAbgenommenAm, String.Empty),
                                If(r.Kommentar, String.Empty)
                           }, entities)
        End Function
        
        Public Function GetJson(params As IDataTable, selectLambda As Expression(Of Func(Of TEntity, String())), entities As IQueryable(Of TEntity)) As JsonResult
            Dim filteredEntities = entities.Filter(params)

            Dim result = From c In filteredEntities.Sort(params).Page(params).ToList().AsQueryable().Select(selectLambda)

            Return json(New With { _
                        .sEcho = params.sEcho, _
                        .iTotalRecords = entities.Count(), _
                        .iTotalDisplayRecords = filteredEntities.Count(), _
                        .aaData = If(result, New String() {}) _
                    }, JsonRequestBehavior.AllowGet)


        End Function
End Class

 

Wer in seiner ASPX MVC Applikationen anwenderfreundliche Operationen auf Listen bereitstellen möchte, dem kann ich DataTables wirklich ans Herz legen. Es macht richtig Spaß damit zu arbeiten.

1 Comments

  1. Pingback: ASPX MVC und DataTables - - SharePoint Blogs in German - Bamboo Nation

Leave a comment

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Time limit is exhausted. Please reload the CAPTCHA.