Kendo UI for ASP.NET MVC : Building a Forum Browser

This website is no longer actively supported

Written by John DeVightJohn DeVight on 01 Jun 2012 15:45

Download Source Code

Overview

Telerik has released the Beta for Kendo UI for ASP.NET MVC, a library for ASP.NET MVC developers that will replace the Telerik Extensions for ASP.NET MVC. The Kendo UI Web JavaScript library is a fantastic UI library and the Kendo UI for ASP.NET MVC library makes it easier for ASP.NET MVC developers to get started with it by providing HTML Helper classes to render the Kendo UI Web controls. However, becoming familiar with the Kendo UI Web JavaScript library will allow to go beyond simply using the HTML Helper classes to render the Kendo UI Web controls as you will see in this article.

This article walks through my experience with using the Beta for Kendo UI for ASP.NET MVC to build an ASP.NET MVC 3 application to browse the Kendo UI Forums. Here is a screenshot:

forumbrowser.png

Configuring an ASP.NET MVC Application with Kendo UI for ASP.NET MVC

Telerik provides some instructions on configuring an ASP. NET MVC Application at: Kendo UI for ASP.NET MVC : Introduction. However, I found a few differences in what I had to do from Telerik's documentation. This is what I did:

  • Create an ASP.NET MVC 3 Application called ForumBrowser
  • Download and Install Kendo UI for ASP.NET MVC
  • Copy the \Binaries\Mvc3\Kendo.Mvc.dll to the ForumBrowser\bin folder
  • Add a reference to ForumBrowser\bin\Kendo.Mvc.dll
  • Copy the follwing Kendo UI JavaScript files from \Scripts to the ForumBrowser\Scripts\kendo folder: Difference is that I only copied a handful of script files
    • jquery.min.js
    • kendo.web.min.js
    • kendo.aspnetmvc.min.js
  • Copy the Kendo UI CSS files from \Content to the ForumBrowser\Content\kendo folder
  • Configure your ASP.NET MVC layout page to use the Kendo UI scripts and themes: Difference is that the filenames listed in the documentation are not the same as the names found in the download. The downloaded files are minimized and have ".min".
<link rel="stylesheet" href="@Url.Content("~/Content/kendo/kendo.common.min.css")"> 
<link rel="stylesheet" href="@Url.Content("~/Content/web/kendo.default.min.css")"> 
<script src="@Url.Content("~/Scripts/jquery.min.js")"></script> 
<script src="@Url.Content("~/Scripts/kendo.web.min.js")"></script> 
<script src="@Url.Content("~/Scripts/kendo.aspnetmvc.min.js")"></script>

Implementing the Splitter

Rendering the Splitter

I used Splitters to divide the page into 3 panes. The left pane is used for navigation. The top right pane displays a list of the forum posts. The bottom right pane displays the details of the selected forum post. I put the Spllitter in the Layout.cshtml page. The main splitter is divided into 2 panes; one for Navigation and one for the nested splitter. The navigation pane renders a partial view for the navigation. The nested splitter contains 2 panes, one for the body, and one for a section called Details. I was unable to get the nested splitter to be defined with in a pane Content of the main splitter without throwing an error, so I defined a helper function called RenderContentSplitter. Here is the code:

@(Html.Kendo().Splitter()
    .Name("mainSplitter")
    .Panes(panes =>
    {
        panes.Add()
            .Size("300px")
            .Content(
                @<text>
                    @Html.Partial("_Navigation")
                </text>
            );
        panes.Add()
            .Content(
                @<text>
                    @RenderContentSplitter()
                </text>
            );
    })
)
 
@helper RenderContentSplitter()
{
    @(Html.Kendo().Splitter()
        .Name("contentSplitter")
        .Orientation(SplitterOrientation.Vertical)
        .Panes(contentPanes =>
        {
            contentPanes.Add()
                .Content(
                    @<text>
                        @RenderBody()
                    </text>
                );
            contentPanes.Add()
                .Size("200px")
                .Content(
                    @<text>
                        @RenderSection("Details")
                    </text>
                );
        })
    )
}

Resizing the Splitter

I wanted the Splitter to fill the entire page. To do this, I wrote some JavaScript. I added a <script> section before the body closing tag with a jQuery.ready() function that calls Layout.init. I then create the Layout static class with a Layout.init function and a Layout.resize function. The Layout.resize function gets called very second to determine whether the splitter needs to be resized.

Here is the jQuery.ready() function defined in the _Layout.cshtml view:

jQuery(document).ready(function () {
    Layout.init();
})

Here is the code for the Layout.js:

var Layout = {}
Layout.__namespace = true;
 
Layout._height = null;
 
Layout.init = function () {
    /// <summary>Initialize the layout page.</summary>
 
    $($("#mainSplitter").children("div.k-pane")[0]).css("overflow-x", "hidden");
    setInterval(function () { Layout.resize(); }, 500);
}
 
Layout.resize = function () {
    /// <summary>Resize the splitter to fit the window.</summary>
 
    var height = $(window).height();
    if (height != Layout._height) {
        Layout._height = height;
        var mainSplitter = $("#mainSplitter");
        mainSplitter.height(height - 25);
        mainSplitter.resize();
 
        var contentSplitter = $("#contentSplitter");
        contentSplitter.height(height - 27);
        contentSplitter.resize();
    }
}

Implementing the Navigation Partial View

The Kendo UI Forums are divided into sections. I used the PanelBar to display the sections as panels that contain child panels. The child panels are the Forum topics.

I created a partial view called _Navigation.cshtml. The partial view is rendered by the Layout.cshtml page in the Splitter using the following: @Html.Partial("_Navigation").

Here is the code for _Navigation.cshtml:

@{ Layout = null; }
 
@(Html.Kendo().PanelBar()
    .Name("navigationPanelBar")
    .Items(panels =>
    {
        panels.Add()
            .Text("Kendo UI Framework")
            .Items(childPanels =>
            {
                childPanels.Add().Text("Data Source").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'699'})" });
                childPanels.Add().Text("Drag and Drop").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'701'})" });
                childPanels.Add().Text("Globalization").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'760'})" });
                childPanels.Add().Text("Templates").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'700'})" });
                childPanels.Add().Text("Validation").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'759'})" });
            });
 
        panels.Add()
            .Text("Kendo UI Web")
            .Items(childPanels =>
            {
                childPanels.Add().Text("General Discussions").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'720'})" });
                childPanels.Add().Text("AutoComplete").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'703'})" });
                childPanels.Add().Text("Calendar").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'729'})" });
                childPanels.Add().Text("Chart").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'704'})" });
                childPanels.Add().Text("ComboBox").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'705'})" });
                childPanels.Add().Text("Date/Time Pickers").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'730'})" });
                childPanels.Add().Text("DropDownList").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'706'})" });
                childPanels.Add().Text("Grid").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'707'})" });
                childPanels.Add().Text("Menu").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'708'})" });
                childPanels.Add().Text("NumericTextBox").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'757'})" });
                childPanels.Add().Text("PanelBar").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'709'})" });
                childPanels.Add().Text("Slider").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'710'})" });
                childPanels.Add().Text("Splitter").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'722'})" });
                childPanels.Add().Text("TabStrip").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'712'})" });
                childPanels.Add().Text("TreeView").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'713'})" });
                childPanels.Add().Text("Upload").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'714'})" });
                childPanels.Add().Text("Window").HtmlAttributes(new { onclick = "Navigation.displayForum({id:'723'})" });
            });
    })
)

Implementing the List of Forum Items

To display a list of forum items, I implemented the Grid in the Index.cshtml view. The Grid uses an Ajax dataSource to populate the grid with a list of RssItem objects. Here is the code:

@(Html.Kendo().Grid<ForumBrowser.Models.RssItem>()
    .Name("rssItemGrid")
    .Columns(columns =>
    {
        columns.Bound(m => m.Title);
    })
    .DataSource(dataSource => dataSource.Ajax().Read(read => read.Action("GetItems", "Home")))
    .Selectable(selectable => selectable.Mode(GridSelectionMode.Single))
)

Displaying the Forum List

When a forum is selected, the Navigation.displayForum function is called. The Navigation.displayForum gets a reference to the Kendo UI Grid and updates the grid's dataSource to request the forum items for the selected forum:

/// <reference path="_Layout.js" />
 
var Navigation = {}
Navigation.__namespace = true;
 
Navigation.displayForum = function (e) {
    /// <summary>Display the list of forum posts for the selected forum.</summary>
 
    Index.displayItem();
    var rssItemGrid = $('#rssItemGrid').data('kendoGrid');
    rssItemGrid.dataSource.transport.options.read.url = '/Home/GetItems/' + e.id;
    rssItemGrid.dataSource.read();
}

Displaying the Details for the Selected Forum Item

When a row in the grid is selected, I want to display the details for the forum post. Since I have all the data in the grid's dataSource, I want to be able to capture the grid's change event and display the forum post details using JavaScript. I couldn't see how to define client event handlers in the Grid HTML Helper, so I added some JavaScript code to bind the Grid to the change event.

In the _Layout.cshtml, I render an optional section in within the jQuery.ready() function:

jQuery(document).ready(function () {
    Layout.init();
    @RenderSection("onReadyScripts", false)
})

In the Index.cshtml view, I added a section called onReadyScripts:

@section onReadyScripts
{
    var rssItemGrid = $('#rssItemGrid').data('kendoGrid');
    rssItemGrid.bind("change", Index.RssItemGrid_onChange);
}

When the application runs, the contents defined in the Index.cshtml, onReadyScripts section replaces the @RenderSection("onReadyScripts", false) in the _Layout.cshtml and will end up looking like this:

jQuery(document).ready(function () {
    Layout.init();
    var rssItemGrid = $('#rssItemGrid').data('kendoGrid');
    rssItemGrid.bind("change", Index.RssItemGrid_onChange);
})

Displaying the Details for the Selected Forum Item

The Index.RssItemGrid_onChange function gets the selected item from the grid and then calls the Index.displayItem to display the item details. The item details is rendered in the Details section:

@section Details {
    <div class='k-widget k-alt' style='padding: 5px 5px 5px 5px;visibility:hidden;'>
        <a id='url'></a>
    </div>
    <div id="description" style='padding: 5px 5px 5px 5px;'>
    </div>
}

Here is the JavaScript code:

var Index = {}
Index.__namespace = true;
 
Index.RssItemGrid_onChange = function (e) {
    /// <summary>When an item is selected in the grid, display the forum post details.</summary>
 
    var selected = $.map(this.select(), function (item) {
        return $(item).text();
    });
    var rssItem = $('#rssItemGrid').data('kendoGrid').dataSource._data[this.select().index()];
    Index.displayItem(rssItem);
}
 
Index.displayItem = function (rssItem) {
    /// <summary>Display the selected RSS Item.</summary>
 
    if (rssItem != undefined) {
        $('#url').attr('href', rssItem.Link).text(rssItem.Link).closest('div').css('visibility', 'visible');
        $('#description').html(rssItem.Description);
    } else {
        $('#url').closest('div').css('visibility', 'hidden');
        $('#description').html('&nbsp;');
    }
}

Implementing the Controller

The Controller has an Index Controller Action and a GetItems Controller Action. Index displays the Index.cshtml page. GetItems gets the forum items from the appropriate Kendo UI Forum and returns the forum items:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
using Kendo.Mvc.UI;
using ForumBrowser.Models;
 
namespace ForumBrowser.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/
 
        public ActionResult Index()
        {
            return View();
        }
 
        public ActionResult GetItems([DataSourceRequest] DataSourceRequest request, string id)
        {
            IList<RssItem> items = null;
 
            if (id != null)
            {
                RssFeed rssFeed = RssReader.Read(string.Format("http://www.kendoui.com/forums/rss.aspx?forumThreadId={0}&rssType=1", id));
                items = rssFeed.Channel.Items;
            }
            else
            {
                items = new List<RssItem>();
            }
 
            return Json(new DataSourceResult { Data = items });
        }
    }
}

Implementing the RssReader

The RssReader class uses the WebClient class to retrieve the forum rss feed. I used an XmlSerializer to deserialize the XML into a RssFeed object. Here is the RssReader:

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Xml.Serialization;
 
namespace ForumBrowser.Models
{
    public static class RssReader
    {
        public static RssFeed Read(string url)
        {
            RssFeed rssFeed = null;
 
            WebClient myWebClient = new WebClient();
            byte[] rssBuffer = myWebClient.DownloadData(url);
            string rssText = Encoding.ASCII.GetString(rssBuffer);
 
            using (TextReader s = new StringReader(rssText))
            {
                var deserializer = new XmlSerializer(typeof(RssFeed));
                rssFeed = deserializer.Deserialize(s) as RssFeed;
            }
            return rssFeed;
        }
    }
}

Here is the RssFeed:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Serialization;
 
namespace ForumBrowser.Models
{
    [XmlRoot(ElementName = "rss")]
    public class RssFeed
    {
        [XmlElement(ElementName = "channel")]
        public RssChannel Channel { get; set; }
    }
 
    public class RssChannel
    {
        [XmlElement(ElementName = "title")]
        public string Title { get; set; }
 
        [XmlElement(ElementName = "link")]
        public string Link { get; set; }
 
        [XmlElement(ElementName = "item")]
        public List<RssItem> Items { get; set; }
    }
 
    public class RssItem
    {
        [XmlElement(ElementName = "guid")]
        public string Guid { get; set; }
 
        [XmlElement(ElementName = "title")]
        public string Title { get; set; }
 
        [XmlElement(ElementName = "link")]
        public string Link { get; set; }
 
        [XmlElement(ElementName = "description")]
        public string Description { get; set; }
    }
}

Conclusion

The Kendo UI for ASP.NET MVC Library has HTML Helpers that are a lot like the Telerik Extensions for ASP.NET MVC that makes the transition much easier for those of us who have used Telerik Extensions for ASP.NET MVC. However, the big benefit that I see with the Kendo UI for ASP.NET MVC Library is that the HTMl Helpers are simply a wrapper around the Kendo UI Web JavaScript library and that the JavaScript library can stand alone. This gives me a lot more control over the Kendo UI Web controls, and if I needed more control over how the controls are defined, I can use the JavaScript API instead of the HTML Helpers to accomplish what I need.


Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License