Asynchronous TreeView in ASP.NET WebForms
Some time ago I have written about using jQuery TreeView plugin for creating an asynchronous TreeView in ASP.NET MVC. Recently I had to use the same plugin in WebForms application, so I decided to share my experience. First let's reference all JavaScript and CSS needed:
First way is to modify the plugin in such a way that it will work with PageMethod requirements (I would recommend this way, as others should be considered nasty workarounds). This is modified load funtion from jquery.treeview.async.js (I'm also changing request from GET to POST here):
You can download source code from repository.
<script src="Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>We should also add the markup for TreeView (an empty list):
<script src="Scripts/jquery.treeview.min.js" type="text/javascript"></script>
<script src="Scripts/jquery.treeview.async.js" type="text/javascript"></script>
<link href="~/Styles/jquery.treeview.css" rel="stylesheet" type="text/css" />
<ul id="trFileBrowser"></ul>Now we should provide data for TreeView, there are generally two approaches in which we can do that.
WebService approach
If we want to use WebService as the data source, we should start by adding an Ajax-enabled WCF Service to the project:[ServiceContract(Namespace = "AsyncTreeViewExample")]and enabling the Web programming model for this service (in web.config):
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class TreeView
{
...
}
In order to write WebService method properly, we need to remember that plugin performs GET request with application/x-www-form-urlencoded content type and expects bare JSON serialized array in response. Knowing all that, the method should look like this (TreeViewNode class was described in previous post about this plugin):
...
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="AsyncTreeViewExample.TreeViewAspNetAjaxBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
</behaviors>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
<services>
<service name="AsyncTreeViewExample.TreeView">
<endpoint address="" behaviorConfiguration="AsyncTreeViewExample.TreeViewAspNetAjaxBehavior" binding="webHttpBinding" contract="AsyncTreeViewExample.TreeView" />
</service>
</services>
</system.serviceModel>
...
Last thing to do in this approach is adding some JavaScript for initialization purposes:
[OperationContract]
//Accept GET request and return bare JSON serialized result
[WebInvoke(Method = "GET", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Json)]
public List<TreeViewNode> FileBrowserData(string root)
{
DirectoryInfo rootDirectory = null;
if (root == "source")
rootDirectory = new DirectoryInfo(@"E:\");
else
rootDirectory = new DirectoryInfo(root);
var directoryChildren = from child in rootDirectory.GetFileSystemInfos()
orderby child is DirectoryInfo descending
select child;
List<TreeViewNode> nodes = new List<TreeViewNode>();
foreach (FileSystemInfo directoryChild in directoryChildren)
{
bool isDirectory = directoryChild is DirectoryInfo;
nodes.Add(new TreeViewNode()
{
id = directoryChild.FullName,
text = directoryChild.Name,
classes = isDirectory ? "folder" : "file",
hasChildren = isDirectory
});
}
return nodes;
}
<script type="text/javascript">
$(document).ready(function () {
$('#trFileBrowser').treeview({
url: '<% = ResolveClientUrl("~/TreeView.svc/FileBrowserData") %>'
});
});
</script>
PageMethod approach
Using PageMethod as a source of data is a little bit more tricky. First of all PageMethods requires request to have application/json content type (so all the parameters have to be properly JSON serialized). Also PageMethod always returns response with wrapped body. There are three ways in which we can workaround this.First way is to modify the plugin in such a way that it will work with PageMethod requirements (I would recommend this way, as others should be considered nasty workarounds). This is modified load funtion from jquery.treeview.async.js (I'm also changing request from GET to POST here):
function load(settings, root, child, container) {After that we just need a regular PageMethod:
$.ajax({
//Changing request to POST
type: "POST",
url: settings.url,
//Required content type
contentType: 'application/json; charset=utf-8',
//Properly JSON serialized data
data: '{"root": "' + root + '"}',
dataType: 'json',
success: function(response) {
function createNode(parent) {
var current = $("<li/>").attr("id", this.id || "").html("<span>" + this.text + "</span>").appendTo(parent);
if (this.classes) {
current.children("span").addClass(this.classes);
}
if (this.expanded) {
current.addClass("open");
}
if (this.hasChildren || this.children && this.children.length) {
var branch = $("<ul/>").appendTo(current);
if (this.hasChildren) {
current.addClass("hasChildren");
createNode.call({
text:"placeholder",
id:"placeholder",
children:[]
}, branch);
}
if (this.children && this.children.length) {
$.each(this.children, createNode, [branch])
}
}
}
//Accessing wrapped array
$.each(response.d, createNode, [child]);
$(container).treeview({add: child});
}
});
}
[System.Web.Services.WebMethod]and initalization JavaScript:
public static List<TreeViewNode> FileBrowserData(string root)
{
//Method body is the same as in WebService
...
}
<script type="text/javascript">Second way we can call a 'client-side workaround'. The idea is to change some default settings for all Ajax requests (it's nasty and I wouldn't recommend using it):
$(document).ready(function () {
$('#trFileBrowser').treeview({
url: '<% = ResolveClientUrl("~/PageMethodPlugin.aspx/FileBrowserData") %>'
});
});
</script>
We also have to allow GET verb on PageMethod:
<script type="text/javascript">
$(document).ready(function () {
//Default callback for creating the XMLHttpRequest object
var xhr = $.ajaxSettings.xhr;
//Changing default values for all Ajax requests
$.ajaxSetup({
//Required content type
contentType: 'application/json; charset=utf-8',
//New callback for creating the XMLHttpRequest object
xhr: function () {
//Wrapping url parameters with '"'
if (this.url.indexOf('=') >= 0) {
this.url = this.url.replace(/=/g, '="') + '"';
}
//Calling default callback
return xhr.call(this);
},
//Sanitize the response (remove wrapping)
dataFilter: function (data, dataType) {
if (dataType == "json") {
var result = $.parseJSON(data);
if (result.d) {
return result.d;
} else {
return data;
}
} else {
return data;
}
}
});
$('#trFileBrowser').treeview({
url: '<% = ResolveClientUrl("~/PageMethodClient.aspx/FileBrowserData") %>'
});
});
</script>
Third way is 'server-side workaround'. We will write PageMethod in a little bit different way, so it will work with TreeView:
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod(UseHttpGet = true)]
public static List<TreeViewNode> FileBrowserData(string root)
{
//Method body is the same as in WebService
...
}
[System.Web.Services.WebMethod]What is left is few lines of JavaScript:
[System.Web.Script.Services.ScriptMethod(UseHttpGet = true)]
//Method doesn't take any parameters and return anything
public static void FileBrowserData()
{
//We are getting 'root' directly from request params
string root = HttpContext.Current.Request.Params.Get("root");
//Nodes generation code is the same as in other cases
...
//We are serializing nodes
JavaScriptSerializer serializer = new JavaScriptSerializer();
string nodesSerialized = serializer.Serialize(nodes);
//And writing them directly into response
HttpContext.Current.Response.Clear();
HttpContext.Current.Response.ContentType = "application/json; charset=utf-8";
HttpContext.Current.Response.AddHeader("Content-Length", nodesSerialized.Length.ToString());
HttpContext.Current.Response.AddHeader("Connection", "Close");
HttpContext.Current.Response.Write(nodesSerialized);
HttpContext.Current.Response.Flush();
}
<script type="text/javascript">Now we have all approaches covered, the result of this work looks like this:
$(document).ready(function () {
//Changing default values for all Ajax requests
$.ajaxSetup({
//Required content type
contentType: 'application/json; charset=utf-8'
});
$('#trFileBrowser').treeview({
url: '<% = ResolveClientUrl("~/PageMethodClient.aspx/FileBrowserData") %>'
});
});
</script>
You can download source code from repository.