Implementatie Telerik MVC Extensions TreeView

Hoewel  met de 3e versie van Asp.Net MVC er weer een aantal HTML
helpers bijgekomen zijn om data uitdagend te representeren, is er nog geen
standaard TreeView.

Gelukkig heeft Telerik een aantal aantrekkelijke extensions voor noppes in de
aanbieding en daartussen zit ook een TreeView.

tree view

Ik moest voor een project een TreeView implementeren en wilde hiervoor de
bewuste Telerik oplossing toepassen. Helaas besteedt Telerik minder aandacht aan hun documentatie en training-materiaal dan aan de kwaliteit van hun extensions. Gelukkig kreeg ik flinke steun van Telerik’s Phil Japikse en Carl Bergenhem. Omdat het eigenlijk redelijk eenvoudig is om deze HTML helper toe te passen heb ik hier een korte introductie samengesteld.

Let op: De Telerik extensions die via Nuget te benaderen zijn, werken niet
(direct) met MVC2 . Deze verwijzen graag naar MVC3.

We gaan eerst de TreeView vullen via ajax callbacks.

Dus start een nieuw Asp.Net MVC3 project:

add mvc3 project

Voeg hier direct de Telerik MVC Extensions aan toe via onvolprezen Nuget
manager:

Nuget

Op naar de site.master. Voeg daar binnen de Head net onder de jquery link toe:

<%Html.Telerik().StyleSheetRegistrar().DefaultGroup(group => group.Add("telerik.common.css")
  .Add("telerik.web20.css"))
  .Render();
%>

En voeg net voor het einde vande </body> toe:


<%= Html.Telerik().ScriptRegistrar() %>

Dit maakt ons project klaar het het toepassen van de Telerik Extensions.

De telerik extensions zijn als het goed is, al bekend binnen de ASPX
viewengine. De namespace is inmiddels toegevoegd aan de web.config van ons
project.

Maar bij MVC3 is er ook de Razor viewengine. daarvoor moet de namespace
referentie nog even opgelost worden. Voeg dan  <system.web.webPages.razor>
toe binnen de web.config in \views\Shared.

Dan zijn we nu eindelijk toegekomen aan de toepassing van de TreeView.

Eerst gaan we naar de HomeController. We hebben een Backend methode nodig om steeds een lijst van child treeviewitems op te hoesten. Deze wordt via een ajax callback steeds aangeroepen als we een node openklappen:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult _AjaxTreeviewLoading(TreeViewItem node)
{
  // create the same over and over... just for fun 🙂
  IEnumerable nodes =
    new List<TreeViewItem>{
      new TreeViewItem
        {
           Text = "Name",
           Value = "1",        // not really unique 😉
           LoadOnDemand = true,
           Enabled = true
        }};

   return new JsonResult { Data = nodes };
}

Deze methode spuuwt steeds dezelfde node uit maar hier kan ook een nette
lijst opgegeven worden.

Maak uiteindelijk onderstaande wijziging  onderaan de \Home\Index view:

<%= Html.Telerik().TreeView().Name("TreeView").ExpandAll(true)
    .ClientEvents(events => events
    .OnSelect("onSelect"))
    .DataBinding(dataBinding => dataBinding
    .Ajax().Enabled(true).Select("_AjaxTreeviewLoading", "Home"))
%>
<p>
<%: Html.ActionLink("Create New", "Create") %>
|
<a id="edit" href="#">Edit</a>
|
<a id="delete" href="#">Delete</a>
</p>

<script type="text/javascript">
  $(document).ready(function () {
    $('#edit').attr('disabled', true);
    $('#delete').attr('disabled', true);
  });

  function onSelect(e) {
    var id =
      $('#TreeView').data('tTreeView').getItemValue(e.item);

    $('#edit').attr('href', 'Home/edit/' + id).attr('disabled', false);

    $('#delete').attr('href', 'Home/delete/' + id).attr('disabled', false);
  }
</script>

Hier wordt de Telerik MVC TreeView toegevoegd en gebind aan een ajax Call.
Tevens is er een Insert, Edit en een Delete link.

Dit ziet er dan zo uit:

Treeview loaded by ajax callback

Iedere node is een aparte ajax call naar de server. Deze geeft dus steeds een
nieuwe node terug als een enkele child. Dit kan wel even doorgaan…

Ook is een ClientEvent van de TreeView gekoppeld aan de OnSelect functie. De
laast aangeklikte child wordt zo ‘onthouden’ via het onselect event. Hierdoor
krijgen de twee links naar Edit en Delete  steeds een andere referentie.

Dit is een aardige oplossing maar als de treeview erg groot wordt, komen de
links in de verdrukking.

Mijn voorkeur is een aparte Edit en Delete action link per node, net als bij
een regulier html tabel. Dit zou dan opgelost moeten worden met een soort
template per node.

Gebruik van templates is helaas geen optie binnen dit kader: “Now the issue
that he was experiencing below is due to the fact that we do not really have a
Template functionality implemented in the TreeView component quite yet” aldus
Phil.

Het alternatief is het gebruik van een Telerik MVC TreeView in combinatie met
een ViewModel.

Eerst defineren we een class welke binnen het ViewModel een node moet gaan
representeren:

public class Customer
{
  public int CustomerID { get; set; }
  public string Text { get; set; }
  public List<Customer> Children { get; set; }
}

Vervolgens geven we een lijst van deze objecten door in de index methode van
de home controller:

public ActionResult Index()
{
  ViewBag.Message = "Welcome to ASP.NET MVC!";
  IEnumerable nodes =
newList<Customer>{
new Customer {CustomerID = 22
                    ,Text = "aaa"
                    , Children = new List<Customer>()
                   },
      new Customer {CustomerID = 23
                    ,Text = "ccc"
                    , Children = new List<Customer>
                      {
                         new Customer {CustomerID = 25
                                       ,Text = "eee"
                                       , Children = new List<Customer>()
                                      },
                         new Customer {CustomerID = 26
                                       ,Text = "hhhh"
                                       , Children = new List<Customer>()
                                      },
                      }
                   },
      new Customer {CustomerID = 24
                    ,Text = "bbb"
                    , Children = new List<Customer>()
                   }
                };

  return View(nodes);
}

Merk op dat de children hier hardcoded toegevoegd worden. Dit kan later ook
via een recursieve methode opgelost worden. Merk ook op dat de kinderen niet
direct deel uitmaken van de IEnumerable lijst.

Als laatste moet de Index view bijgewerkt worden. We gebruiken hiervoor een
nieuwe treeview helper definitie:


<%= Html.Telerik().TreeView()
.Name("TreeViewByViewModel")
.BindTo(Model,
mappings =>       {
mappings.For<MvcApplication10.Controllers.Customer>(
binding => binding.ItemDataBound(
                      (currentItem, currentCustomer) => {
      currentItem.Text =
currentCustomer.Text +
String.Format(" {0} {1}",
Html.ActionLink("Edit", "Edit", new {
            id = currentCustomer.CustomerID }),
Html.ActionLink("Delete","Delete", new {
            id = currentCustomer.CustomerID }));

currentItem.Encoded = false;

currentItem.Expanded = true;

currentItem.Value = currentCustomer.CustomerID.ToString();
      })
      .Children(currentCustomer => currentCustomer.Children)
);
})
%>

Hierbij wordt de text van iedere node gevuld met een omschrijving en met twee
url’s. Dit wordt geforceerd door de ‘Encoded’ property op false te zetten.

En de children wordt zichtbaar door te refereren naar de Children property
van de Customer.

De TreeView notatie is verre van leesbaar en erg gevoelig voor het tonen van
verkeerde foutmeldingen als er ook maar één karakter verkeerd staat. Let dus
goed op de notatie.

Dit resulteert uiteindelijk in een fraaie treeview met bij iedere node een
link voor de edit en de delete actions.

Treeview loaded by viewmodel and with template

Conclusie: hoewel Telerik HTML helpers relatief eenvoudig toe te passen zijn,
maakt de ingewikkelde notatie deze tot een waar misterie. Maar met wat
doorzettingskracht en een heleboel surfen blijkt dat deze HTML Helpers eigenlijk erg fraaie resultaten geven.