jsTreeview in ASP.NET MVC gevuld met Json action

Op dit moment heeft jQuery UI nog geen treeview control. Als alternatief zijn er enkele andere aanbieders van Treeview controls. In het verleden heb ik al eens bericht over de Telerik controls. Deze kan ik zonder meer aanbevelen maar helaas zijn de gratis extensies alleen voor open source projecten toe te passen.

Een andere treeview is de jsTreeview. Deze is heel aardig (ondersteunt ajax, de jQuery themes en bv. ook checkboxes). Helaas is de deocumentatie nogal fragmentarisch. Daarom geef ik hier een opstap hoe jsTreeview is toe te passen in ASP.NET MVC. We maken hierbij gebruik van Json actions om de kinderen op te vragen vanuit de controller.

Stap 1: Bestanden plaatsen

Haal eerst de laatste versie op vanaf http://www.jstree.com/ . Dit is de versie pre 1.0 fix 2.

Plaats in het ASP.NET MVC4 project de jquery.jstree.js in de \Scripts map.

Plaats d.gif (voor ie6 gebruik), d.png, styles.css en throbber.gif in \Scripts\themes\treeview map.

Stap 2: bundles aanpassen

Vul de bundles aan in de BundleConfig.cs:

bundles.Add(new StyleBundle("~/Scripts/themes/treeview").Include(
                         "~/Scripts/themes/treeview/style.css"));
bundles.Add(new ScriptBundle("~/bundles/jqueryplugins").Include(
                        [skip other plugins],
                        "~/Scripts/jquery.jstree.js"));

En deze bundles moeten op de _Layout.cshtml aangeroepen worden:

…
 @Styles.Render("~/Scripts/themes/treeview")
…
</head>
<body>
…
 @Scripts.Render("~/bundles/jquery", "~/bundles/jqueryui", "~/bundles/jqueryplugins")
…

Stap 3: HTML placeholder

Plaats een placeholder op de _Layout.cshtml

<div id="treeviewEdit"> Tree view placeholder</div>

Stap 4: JavaScript

Plaats daarna het volgende  script onder aan de _Layout.cshtml pagina  om te worden uitgevoerd als de pagina laadt (dit moet dus in een javascript blok):

$(function () {
    $("#treeviewEdit").jstree({
        "json_data": {
            "ajax": {
                "url": "/Home/FillEditTree",
                "data": function (n) {
                    return { id: n.attr ? n.attr("id") : 0 };
                }
            }
        },
        "themes": {
            dots: false,
            icons: true
        },
        "plugins": ["themes", "json_data"]

    });
});

Deze jQuery functie zal op de treeview div losgelaten worden om het treeview tekenen te forceren.

Stap 5: de (view) modellen

We praten straks in Json formaat met de treeview, maar we willen dit in typed classes samenstellen. Voeg dus de volgende viewmodellen toe:

jstreeview1

De code wordt dan:

public class JsTreeModel
{
    public JsTreeData data { get; set; }

    public JsTreeAttribute attr { get; set; }

    // "open" or "closed" or null (not available for ajax)
    public string state { get; set; }

    public List<JsTreeModel> children { get; set; }
}
public class JsTreeAttribute
{
    public string id { get; set; }
}
public class JsTreeData
{
    public string title { get; set; }

    public JsTreeDataAttribute attr { get; set; }

    // "folder"  or "/" for no icon thus file
    public string icon { get; set; }
}
public class JsTreeDataAttribute
{
    public string href { get; set; }
}

Laatste stap 6: Controller action

Voeg de volgende action toe aan de controller. Deze geeft ter demonstratie als antwoord altijd de opgevraagde node met daaronder twee kinderen. De eerste keer wordt node 0 opgevraagd. het eerste kind heeft zelf ook kinderen (niet meegestuurd in deze aanroep) en de tweede heeft geen kinderen:

public ActionResult FillEditTree(int id)
{
    // just to show some nodes
    var rootId = id + 2;

    var children = new List<JsTreeModel>();

    // first child containing one or more children
    children.Add(
        new JsTreeModel
            {
                attr = new JsTreeAttribute { id = (rootId + 1).ToString() },
                data = new JsTreeData
                        {
                            title = "Child " + (rootId + 1).ToString(),
                            attr =
                                new JsTreeDataAttribute
                                    {
                                        href =
                                            @"\home\edit\"
                                            + (rootId + 1).ToString()
                                    }
                        },
                state = "closed",
            });

    // second child containing NO children
    children.Add(
        new JsTreeModel
            {
                attr = new JsTreeAttribute { id = (rootId + 2).ToString() },
                data = new JsTreeData { icon = "/", title = "Child " + (rootId + 2).ToString(), },
            });

    // de ‘root’
    var requestedSubTree = new List<JsTreeModel>();

    requestedSubTree.Add(
            new JsTreeModel()
            {
                attr = new JsTreeAttribute { id = rootId.ToString() },
                state = "open",
                data =
                    new JsTreeData
                        {
                            title = "Title " + rootId.ToString(),
                            attr =
                                new JsTreeDataAttribute { href = @"\home\edit\23" }
                        },
                children = children
            });

    return Json(requestedSubTree, JsonRequestBehavior.AllowGet);
}

Dit geeft het volgende plaatje (na enkele keren kinderen opvragen):

jstreeview2

Er zijn een aantal zaken die opvallen:

  • De ‘root’ moet standaard de state geopend (“open”) krijgen
  • Je moet dus onderzoeken of de getoonde kinderen op hun beurt ook kinderen hebben
    • De kinderen moeten standaard de state te openen (“closed”) krijgen als deze op hun beurt kinderen hebben.
    • Een kind zonder kinderen moet geen state krijgen (dus null). Dan komt er ook geen ajax call mogelijkheid.

Bonus stap 7: jQuery Thema

Als laatste in het rijtej plugins kan “themeroller” toevoegen worden.  Deze kan je toepassen als alternatief voor thema:

$(function () {
    $("#treeviewEdit").jstree({
        [skip],
        "themes": {
            dots: false,
            icons: true
        },
        "plugins": ["json_data", "themeroller"]
    });
});

Dit geeft een aardige uitbreiding op het standaard aanwezige thema:

jstreeview3

Bonus stap 8: cookies

Je kunt ook plugin “cookies” toepassen. Dan wordt bij een F5 de selectie bewaard en niet opnieuw opgevraagd. Maar de laatste kan ook op de server in de sessie onthouden worden en bij de initiële aanroep (node id is 0) toegepast kunnen worden.

Conclusie 

Deze plugin is heel aardig om voor weinig geld en snel een treeview toe te passen in ASP.NET MVC.

Seeing anything you like? Google translate does not work out? Drop me a note and I will translate this post.

Ajax als plakband voor het internet

In het recente verleden heb ik met gemende gevoelens te maken gehad met
JavaScript. Ook heb ik hierbij Ajax moeten toepassen in websites. Ik vind het
gewoon plakband voor websites.

Waarom?

Websites gedragen zich fundamenteel anders dan ‘klassieke’ WinForm
applicaties en ook gedragen zij zich anders dan de moderne WPF variant. Websites zijn gebaseerd op technieken van pakweg tien-twintig jaar terug waarin browsers nog in de kinderschoenen stonden, bandbreedte en servercapaciteit schaars was en browsers ronduit dom waren.

Daartegenover staat dat gebruikers van bv. administratieve systemen rijke
interfaces met veel interactie gewend zijn. Het grootste deel van de huidige
generatie gebruikers is opgegroeid in de hoogtijdagen van het client/server
tijdperk en wat hun betreft was dat een heel aardige werkomgeving. Dat
tegenwoordig de meeste applicaties juist als webapplicatie gebouwd wordt, is
vaak ingegeven door enigzins gedateerde ideeën over de vereenvoudigde uitrol
(nooit eens naar Click-Once gekeken?), de ‘denkbeeldige’ eenvoud van het ontwerp door de gelaagdheid en het feit dat de hele wereld er achteraan rent dus het zal wel goed zijn. Dat de ontwikkeling vaak veel lastiger is dan een WinForm
applicatie, omdat voortdurend tussen alle losse lagen met data geschoven moet
worden en dat de ontwikkeltijd meestal langer is, daar wordt gewoon aan voorbij
gegaan.

Nu is een website bouwen op zich niet lastig. Met een gemiddelde
ontwikkelomgeving wordt het tegenwoordig redelijk triviaal om dmv. een MVC
(Model View Controller) of zelfs een MVP (Model View Presenter) patroon een
applicatie in elkaar te zetten. Dit gecombineerd met technologie zoals WCF maakt betrouwbare datauitwisseling steeds eenvoudiger.

Het grootste probleem is juist dat deze glimmende webapplicatie zich moeten
gedragen als de eerste de beste client/server WinForm applicatie. Veel
webapplicatie zijn vervangingen van bestaande WinForm applicaties waarbij de
nodige uitbreidingen met Client/Server in het achterhoofd zijn uitgedacht. Want
het tweede probleem is dat veel webapplicaties ontworpen worden met de vereisten van een Client/server applicatie. De technologie is met sprongen vooruit gegaan, de ontwerpers zitten nog met hun hoofd en ideeën in de jaren tachtig/negentig van de vorige eeuw.

Dit betekent dat ondanks de zegeningen van multi-tier gelaagdheid,
server-side pagina opbouw van pagina’s en de aanwezigheid van prachtige
services, de frontend (de browser dus) nog steeds grote hoeveelheden dat moet
blijven tonen op het scherm. En hierbij moet de volledige GUI in sync gehouden
worden, afhankelijk van de interactie door de gebruiker. We zitten dus met
enorme lijsten in tabellen, het enabelen en disabelen van knoppen en een
dynamische toegang tot data (Iedere interactie van de gebruiker moet hele
schermen op hun kop zetten).  En er is ook nog de vereiste dat de data per
direct voor de gebruiker beschikbaar moet zijn. De round-trip naar de server
moet daarbij nihil zijn terwijl de duur daarvan juist een direct gevolg is van
die uitbundige gui.

In plaats van het opvoeden van de klant (lees: de ontwerper) hebben we dus
ons heil gezocht is plakband om website pagina’s er als een winform uit te laten
zien. Natuurlijk hebben wij ontwikkelaars hier zelf ook een hand in gehad. We
willen pleasen dus zeggen geen nee en het is natuurlijk fraai om asynchroon data
op te halen compleet met animated gifs om de gebruiker af te leiden totdat de
aanroep afgewerkt is.

Genoeg gebashed… Dus u wilt Ajax? Dan krijgt u Ajax.

In het verleden heb ik nog gebruik gemaakt van Ajax Pro
(http://www.ajaxpro.info/). Voldeed prima aan de verwachtingen en was redelijk eenvoudig op te nemen in het webproject. Met het eenvoudig in javascript aanroepen van een asp.net methode op een aspx pagina kon serverside data verzameld en opgehaald worden. Ook hebben we toen nog naar Anthem.Net gekeken.
Deze had een hele set van aardige webcontrols welke Ajax enabled waren. Asp.Net AJAX van MS viel helaas af want we zaten toen nog in VS2003.

Maar met de komst van het Ajax updatepanel, de Ajax extensions en zelfs de
Ajax controls is Ajax veel toegankelijker geworden. Dit is nu helemaal triviaal
met VS2008 waar het standaard in opgenomen is. We kunnen nu dus helemaal los om er een zooi op de client van te maken 🙂 Download dus snel de extra
toolkit .

Toch is er voor de liefhebber van eenvoudig lichtgewicht Ajax gebruik vanuit
JavaScript een tussenoplossing en die wordt hieronder getoond in acht eenvoudige stappen:

Stap 1

Open Visual Studio 2008 en start een nieuw webproject.

Stap 2

Voeg een ScriptManager (op tabblad AJAX Extensions van de Toolbox) toe binnen de HTML Form op de Default.Aspx:


<asp:ScriptManager ID="ScriptManager1" runat="server"
EnablePageMethods="true"></asp:ScriptManager>

  Stap 3

EnablePageMethods moet dus aangezet worden. In VS2008 is dit voldoende, Extra HttpHandlers of web.config aanpassingen zijn niet meer nodig zoals vroeger… de web.config is al aardig voorbereid.

Stap 4

Op de server side webpagina _Default.aspx plaats je een static webmethod:

[WebMethod]
public static string[] GeefLijst(int lengte)
{
  string[] lijst = new string[lengte];

  for (int i = 0; i < lengte; i++)
  {
    lijst[i] = i.ToString();
  }

  return lijst;
}

Dit geeft eenvoudig een array van strings terug.

En zet alvast ter illustratie een breakpoint in de WebMethod.

Stap 5

Deze WebMethod roep je aan in een JavaScript functie (bv. geplaatst in de
HTML Head)

<script type="text/javascript">
  function voerPageMethodUit() {
    alert('voerPageMethodUit');
PageMethods.GeefLijst(5, geefLijstResult);
  }

  ...
</script>

Zie dat de methode GeefLijst voorafgegaan wordt door de ‘namespace’
PageMethods en niet _Default. Ook consumeren we (nog) niet de geretourneerde waarde van de WebMethod. Let er ook op dat de JavaScript functies met een kleine letter beginnen, in tegenstelling tot de WebMethod.

Stap 6

De aanroep is asynchroon (het is tenslotte Ajax) dus je moet bij de
terugkomst van de serveraanroep de response verwerken. Hiervoor is een extra
methode aanwezig welke bij de aanroep bekend is gemaakt:

function geefLijstResult(result) {
  alert('Server gaf resultaat: ' + result);
}

Een naamconventie is nu wel handig om de twee methodes aan elkaar te
koppelen: …Result geeft hier aan dat het een asynchrone response methode is
van de call-back.

Stap 7

Alles staat nu klaar voor aan aanroep, nu komt alleen nog het toepassen. Roep
dus de JavaScript functie aan, bv. in het onload event van de HTML body.

<body onload=" voerPageMethodUit()" >

Stap 8

Start nu de website (bij voorkeur met de optie tot debuggen aangezet). Als
alles klopt wacht nu de pagina na het opstarten direct op de eerst alert. Daarna
komen we in de breakpoint in de WebMethod op de server en als laatste wordt de inhoud van de array getoond door de alert in de response method.

Tadaah, een Ajax callback in JavaScript is echt eenvoudig geworden via de
ScriptManager. Kijk voor meer details op: http://msdn.microsoft.com/en-us/library/bb398995.aspx