In this series of posts I will describe how I build a web part full of AJAX functionality using jQuery and some plugins for jQuery.

###Why?

ASP.Net AJAX is quite hard to implement using only code. Besides that, having multiple updatepanels and multiple triggers outside of these updatepanels, can complicate stuff very quickly. So I decided to see how much time I would need to build the required functionality using JavaScript with jQuery.

###What?

The web part of choice was one that would allow visitors of the portal to order a bunch of products from different categories. Products are stored in a list and have a choice sitecolumn to specify the category of the item. Products can be selected and added to a shopping cart. After the wanted items are added to the shopping cart, the user can specify some comments, a handling method (Pick-up or deliver) and a delivery-/pickup date and send in the order. The order information is saved in a list, the user gets a confirmation email and the shopping cart is cleared. The resulting web part look like this (sorry for the dutch interface):

Example interface

###How?

For getting the categories and products we’ll use a generic handler. This offers the possibility of caching and is perfectly suitable for read-only data access. The shopping cart and order functionality will be provided through a ASP.Net Webservice. Both the handler and web service will return JSON data. We’ll need ASP.Net 3.5 for this.

The web part will only override the render method and write out the needed HTML. So no control instantiation, event handling or other code.

###Useful links

Dave Ward’s weblog on encosia.com provides a vital source for information on combining jQuery and ASP.Net. Also Rick Strahl has some useful posts on this subject.

Building the products handler

We’ll start by building the Generic Handler that returns JSON data for categories and products. We’ll differentiate between the two by passing in the type with the query string. I’ve created two classes for data retrieval from the list in SharePoint, one that returns the choices of the categories and one that returns the products based on the category. We’ll then use the DataContractJsonSerializer to generate and return the JSON for this data:

public void ProcessRequest(HttpContext context)
{
	try
	{
		if (string.IsNullOrEmpty(context.Request["type"]))
			throw new ArgumentException("type not specified or null");
		
		string type = context.Request["type"];
		context.Response.Cache.SetExpires(DateTime.Now.AddSeconds(300));
		context.Response.Cache.SetCacheability(HttpCacheability.Public);
		
		if (type.Equals("categories", StringComparison.InvariantCultureIgnoreCase))
		{
			StringCollection categories = BPVProductCategory.GetProductCategories();
			DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(StringCollection));
			ser.WriteObject(context.Response.OutputStream, categories);
		}
		
		if (type.Equals("products", StringComparison.InvariantCultureIgnoreCase))
		{
			if (string.IsNullOrEmpty(context.Request["category"]))
				throw new ArgumentException("category not specified or null");
			
			string category = HttpUtility.UrlDecode(context.Request["category"]);
			Ecabo.Intranet2009.SharePoint.Diagnostics.Logging.LogMessageFormat("Category: {0}", category);
			List products = BPVProduct.GetAvailableProducts(category);
			
			DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(List));
			ser.WriteObject(context.Response.OutputStream, products);
		}
	}
	catch (Exception ex)
	{
		context.Response.Write(string.Format("ERROR: {0}", ex.Message));
	}
}

Building the shopping cart web service

Next, we need to create a web service that will allows us to modify the shopping cart with add, remove, change and clear methods. This web service will also contain a method to place the order. To make sure this web service will return JSON when requested, we’ll decorate the class with the ScriptService attribute (normally you just have to comment out the automatically included line in the class definition) and we decorate the methods with the ScriptMethod attribute in which we specify the ResponseFormat to be JSON. To store the shoppingcart between requests to the service we’ll use the Session. In order for that to work we add the EnableSession=true parameter to the WebMethod attribute of each method. The resulting code looks like this:

[WebService(Namespace = "http://sharepoint.ecabo.nl/200903/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.ComponentModel.ToolboxItem(false)]
// To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
[System.Web.Script.Services.ScriptService]
public class BPVShoppingCart : System.Web.Services.WebService
{
	private List ShoppingCart
	{
		get
		{
			if (HttpContext.Current.Session["BPVShoppingCart"] != null)
			{
				return (List)HttpContext.Current.Session["BPVShoppingCart"];
			}
			else
			{
				List shoppingCart = new List();
				HttpContext.Current.Session.Add("BPVShoppingCart", shoppingCart);
				return shoppingCart;
			}
		}
		set
		{
			HttpContext.Current.Session["BPVShoppingCart"] = value;
		}
	}

	[WebMethod(EnableSession = true)]
	[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
	public List GetItems()
	{
		try
		{
			return ShoppingCart;
		}
		catch (Exception ex)
		{
			Ecabo.Intranet2009.SharePoint.Diagnostics.Logging.LogException(ex);
			return null;
		}
	}
	
	[WebMethod(EnableSession = true)]
	[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
	public List AddItem(string productName, int productId, string productCode, int amount)
	{
		try
		{
			ShoppingCartItem item = ShoppingCart.Find(p => p.ProductID == productId);
			if (item == null)
			{
				item = new ShoppingCartItem();
				item.Amount = amount;
				item.ProductCode = productCode;
				item.ProductID = productId;
				item.ProductName = productName;
				ShoppingCart.Add(item);
			}
			else
			{
				item.Amount += amount;
			}
			return ShoppingCart;
		}
		catch (Exception ex)
		{
			Ecabo.Intranet2009.SharePoint.Diagnostics.Logging.LogException(ex);
			return null;
		}
	}
	
	[WebMethod(EnableSession = true)]
	[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
	public List DeleteItem(int productId)
	{
		try
		{
			ShoppingCartItem item = ShoppingCart.Find(p => p.ProductID == productId);
			if (item != null)
			{
				ShoppingCart.Remove(item);
			}
			return ShoppingCart;
		}
		catch (Exception ex)
		{
			Ecabo.Intranet2009.SharePoint.Diagnostics.Logging.LogException(ex);
			return null;
		}
	}
	
	[WebMethod(EnableSession = true)]
	[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
	public List ChangeAmount(int productId, int amount)
	{
		try
		{
			if (amount == 0)
			return DeleteItem(productId);
			ShoppingCartItem item = ShoppingCart.Find(p => p.ProductID == productId);
			if (item != null)
			{
				item.Amount = amount;
			}
			return ShoppingCart;
		}
		catch (Exception ex)
		{
			Ecabo.Intranet2009.SharePoint.Diagnostics.Logging.LogException(ex);
			return null;
		}
	}
	
	[WebMethod(EnableSession = true)]
	[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
	public List ClearCart()
	{
		try
		{
			ShoppingCart.Clear();
			return ShoppingCart;
		}
		catch (Exception ex)
		{
			Ecabo.Intranet2009.SharePoint.Diagnostics.Logging.LogException(ex);
			return null;
		}
	}
	
	[WebMethod(EnableSession = true)]
	[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
	public List PlaceOrder(string comments, string deliveryType, string deliveryDate)
	{
		try
		{
			string products = "";
			foreach (ShoppingCartItem item in ShoppingCart)
			{
				products += string.Format("{0} - {1} - {2}\r\n", item.ProductCode, item.Amount, item.ProductName);
			}
			
			BPVBestelling bestelling = new BPVBestelling();
			bestelling.Comments = comments;
			bestelling.Handling = deliveryType;
			bestelling.HandlingDate = Convert.ToDateTime(deliveryDate, new CultureInfo("nl-NL"));
			bestelling.Title = DateTime.Now.ToString("yyyyMMdd_HHmm") + "_" + SPContext.Current.Web.CurrentUser.Email;
			bestelling.Products = products;
			bestelling.PlacedBy = SPContext.Current.Web.CurrentUser;
			if (BPVBestelling.AddBestelling(bestelling))
			{
				BPVBestelling.SendConfirmationEmail(bestelling, ShoppingCart);
				ShoppingCart.Clear();
				return ShoppingCart;
			}
			else
			throw new Exception("Het is niet mogelijk om uw bestelling te verwerken");
		}
		catch (Exception ex)
		{
			Ecabo.Intranet2009.SharePoint.Diagnostics.Logging.LogException(ex);
			throw new Exception("Het is niet mogelijk om uw bestelling te verwerken", ex);
		}
	}
}

There’s one more thing needed for letting the web service return the data in JSON format. We need to include a httpHandler in the web.config for the asmx extension that routes the request to the ScriptHandlerFactory. An easy way to do this is for your SharePoint webapp by creating a blank web.config and placing it in a subfolder of the layouts directory where you also place the asmx file. The following is all you need in that web.config file:

<?xml version="1.0" standalone="yes"?>
<configuration>
	<system.web>
	<httpHandlers>
		<add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
	</httpHandlers>
	<compilation debug="true"/></system.web>
</configuration>

###Next parts

That wraps it up for this first post in the series. In part 2 I’ll show you how to call these services from jQuery and insert the data in the HTML of the webpart. In part 3 we’ll enhance the experience by including dialogs and validation in the solution.

Sometimes all the cool effects and functionality you’ve build on top of jQuery turn out to be a bit sluggish. Here are a few tips to optimize performance.

  1. Use jQuery 1.3+. This one utilizes the new Sizzle selector engine which is much faster then the selector engine in the jQuery versions before that.

  2. Make your selectors as specific as possible. Sizzle is build to fail as soon as possible. Using id’s as part of your selector statement can provide quick performance gains. Also specifying a scope for your selector can be beneficial as jQuery only iterates over the elements within the scope.

  3. Reuse selectors and use chaining. Storing the result of a selector in an object variable makes reuse easy. Chaining allows you to perform multiple actions on the already selected DOM elements.

  4. Don’t use class-only selectors. $(“.active”) iterates over all DOM elements. So use a tag name to narrow the search down, $(“li.active”).

  5. Use the profiler plugin by John Resig to profile your jQuery calls.

When you’re building an AJAX control in .Net there a a few possibilities. One of them is using AJAX.Net updatepanels. This saves you from writing tedious javascript code to refresh parts of you page. With the arrival of javascript libraries such as jQuery it’s much easier to create the AJAX functionality you want with javascript. However, you still have to write quite a lot of DOM manipulation code and use string concatenation to process any JSON results and render the correct HTML.

Fortunately some javascript template engines are developed to make this easier. These engines come in all shapes and sizes, ranging from engines with an own templating syntax to simple data binding engines.

On the first side of the spectrum, there are engines such as jTemplates, this one uses python like syntax to create the instructions. On the other end engines like Chain.js and PURE live, these can be considered more a databinding egines. The last ones make use of classnames for the databinding, like in the following Chain.js example:

<div id="quickdemo">
	<div class="item">
		<span class="library">Library Name</span>
	</div>
</div>
$('#quickdemo').items( [
		{library:'Prototype'},
		{library:'jQuery'},
		{library:'Dojo'},
		{library:'MooTools'}
	]).chain();

In this case the library field in the JSON objects is put into the element with “library” in de classname. The nice thing about Chain.js is the fact that it monitors the items collection for changes. Adding or removing items from script, automatically updates the generated HTML. So filtering and sorting can be very easily accomplished, some very easy to follow examples are available on the companion website.

PURE uses the same classnames based system for the databinding. Consider the following example:

<ol class="siteList reference@id">
	<li class="sites">
		<a class="name url@href" href="http://beebole.com">Beebole</a>
	</li>
</ol>
var data = {
	"reference": "3456",
	"sites": [{
			"name": "Beebole",
			"url": "http://beebole.com"
		},
		{
			"name": "BeeLit",
			"url": "http://beeLit.com"
		},
		{
			"name": "PURE",
			"url": http://beebole.com/pure
		}]
	};
$('ol.siteList').autoRender(data);

The url@href and reference@id classnames provide a way to set attributes of the databound elements.

But what if you don’t want to decorate your HTML elements with extra classnames to support the databinding? Or you want to handle some events of the generated elements?

For this PURE supports directives. You create your directives and pass them into the autoRender or render functions. Consider the following example:

<div style="display: none;" id="bpvcategorytemplate">
	<ul>
		<li class="context">
			<a category="" class="context context@category" href="#">laden...</a>
		</li>
	</ul>
</div>
function showProductCategories()
{
    $.getJSON(bpvweburl + "/_layouts/ecabointranet2009/bpv.ashx", {type: "categories"}, function(data)
    {
        var categoriescontainer = $("#bpvcategoriescontainer");
        categoriescontainer.empty();
        var list = categoriescontainer.append($("#bpvcategorytemplate").html());
        var directive = {'a.context[onclick]' : '"showProducts(this); return false;"'}
        list.autoRender( data, directive );
    });
}

Here we get some JSON from a handler, put the html from the template into a new element, and bind the JSON to that template. When binding the JSON data, we attach a javascript function to the onclick event of the generated anchor tags.

Much more can be accomplished by using directives, such as creating an alternating row style by setting a class during the binding of the items. For more information about using directives in PURE, read this page.

There are loads more things possible in PURE or in the other engines, the best way to find out is to read the docs and try some things out.

We had a very strange issue with some search functionality we developed for a portal. We created an option to search for documents in a library by specifying the path to a specific folder.

This is done by specifying a keyword query like 9:”folder/subfolder”. In this case the searchproperty 9 contains the path of the item.

While this worked well on our development environments and most of the browsers we tested on, a some browsers it failed. My colleague Arthur discovered it had something to do with the language setting of the browser. When we used Dutch (nl-NL) as preferred language the standard core search results web part didn’t return any items when using a query with a “/” in the conditions.

If you execute a KeyWordQuery or FullTextSqlQuery in your own code you have the possibility to specify a culture to use in the search. Unfortunately the core search results web part doesn’t provide you with an option to do this easily. In the end I came up with a “hack” to make this work. Just change the preferred languages the browser sends with each request in the OnInit of a webpart that inherits from the standards core results web part like so:

protected override void OnInit(EventArgs e)
{
	if (!string.IsNullOrEmpty(CultureOverrideTo))
	{
		if (this.Page.Request.UserLanguages.Length > 0)
			this.Page.Request.UserLanguages[0] = en-US;
	}
	base.OnInit(e);
}

Note: Because this modifies the current request this can have some consequences on other web parts on your page. So use it with care.

UPDATE:

This same behavior can also occur in custom search code. To fix this, simply specify the Culture of your KeyWordQuery or FullTextSqlQuery.

In part 1 I showed you how to create the basics for a 3D tagcloud in Silverlight. In this part I’ll show how to get the tags from your html for inserting it into a blog template, we’ll change the color of the tag based on the weight of the tag and let the hyperlink button actually function as a hyperlink (until now it does nothing when you click on it).

There are a few different ways you can pass or get information from the page the Silverlight control is hosted in:

  • Set the initParams parameter in the Silverlight object definition
  • Read the HtmlDocument in your Silverlight code
  • Use JavaScript to call methods in your Silverlight code

You can only use the first option if your hosting your Silverlight application on the same domain. When using the hosting service on silverlight.live.com, no interaction between your application and HTML page is allowed.

For the second option you can use the HtmlPage object in your code to traverse through the HTML DOM, basic methods such as GetElementById are available.

The last option is the one I’ll be using in this post. To accomplish this you need to follow a few steps:

  • Decorate your page class with a “ScriptableType” attribute
  • Decorate the method you want to call with the “ScriptableMember” attribute and make sure it’s public
  • Register your object in the HtmlPage
  • Give your Silverlight object definition an id, so you can easily reference it from your JavaScript

The reason I’m going with this technique is the flexibility it provides when you want to reuse the tagcloud in other applications. Every blog engine has a different way to generate the HTML for a tagcloud, my blog just shows a list of tags.

To change the color based on the weight and set the url of the HyperlinkNutton we need to make some changes to the Tag3D object I showed you in the last post:

public class Tag3D
{
    public HyperlinkButton btnLink { get; set; }
    private TextBlock textBlock { get; set; }
    public Point3D centerPoint { get; set; }
    private Color TagColor { get; set; }
    public Tag3D(double x, double y, double z, string text, string url, Color tagColor, int weight)
    {
        centerPoint = new Point3D(x, y, z);
        textBlock = new TextBlock();
        textBlock.Text = string.Format("{0} ({1})", text, weight);
        btnLink = new HyperlinkButton();
        btnLink.Content = textBlock;
        btnLink.NavigateUri = new Uri(url);
        this.TagColor = tagColor;
    }
    public void Redraw(double xOffset, double yOffset)
    {
        double zFactor = ((centerPoint.Z + 300) / 450.0);
        btnLink.FontSize = 30.0 * zFactor;
        double alpha = zFactor * 255;
        //Debug.WriteLine("Z: {0}; zFactor: {1}; alpha: {2}", centerPoint.Z, zFactor, alpha);
        btnLink.Foreground = new SolidColorBrush(Color.FromArgb(Convert.ToByte(alpha), TagColor.R, TagColor.G, TagColor.B));
        Canvas.SetLeft(btnLink, centerPoint.X + xOffset - (btnLink.ActualWidth / 2));
        Canvas.SetTop(btnLink, -centerPoint.Y + yOffset - (btnLink.ActualHeight/ 2));
        Canvas.SetZIndex(btnLink, Convert.ToInt32(centerPoint.Z));
    }
}

In the constructor we now pass in the url and a Color to show when the tag is the most important (I chose to use a scale of 1 to 10, with 10 being the most important). The url is simply assigned to the NavigateUrl property of the HyperlinkButton. The Color is used when setting the new Foreground Brush. I also made some modifications in the calculations of the font size and alpha of the Brush to make it look a bit more realistic. To let the JavaScript in the page add the tags I’ve created a AddTag method and decorated it with the ScriptableMember attribute:

[ScriptableMember()]
public void AddTag(string tag, string url, int weight)
{
    if (weight > 10)
    weight = 10;
    Color color = new Color();
    color.R = Convert.ToByte(Math.Round(209.0 * ( weight / 10.0)));
    color.G = Convert.ToByte(Math.Round(18.0 * (weight / 10.0)));
    color.B = Convert.ToByte(Math.Round(65.0 * (weight / 10.0)));
    tagBlocks.Add(new Tag3D(0.0, 0.0, 0.0, tag, url, color));
}

In this method we calculate the Color of the tag based on the weight. Then we add a new tag to the tagBlocks list.

After calling this method a couple of times we need to place the tags and display them. I’ve changed the FillTags method shown in the previous post and renamed it to ProcessTags to make the name a bit more meaningful:

[ScriptableMember()]
public void ProcessTags()
{
    double radius = RootCanvas.Width / 3;
    int max = tagBlocks.Count;
    double phi = 0;
    double theta = 0;
    for (int i = 1; i < max + 1; i++)
    {
        phi = Math.Acos(-1.0 + (2.0 * i  1.0) / max);
        theta = Math.Sqrt(max * Math.PI) * phi;
        double x = radius * Math.Cos(theta) * Math.Sin(phi);
        double y = radius * Math.Sin(theta) * Math.Sin(phi);
        double z = radius * Math.Cos(phi);
        Tag3D tag = tagBlocks[i -1];
        tag.centerPoint = new Point3D(x, y, z);
        tag.Redraw(RootCanvas.Width / 2, RootCanvas.Height / 2);
        RootCanvas.Children.Add(tag.btnLink);
    }
}

We need one more thing to make the methods callable from JavaScript. Register the object with the HtmlPage in the constructor:

HtmlPage.RegisterScriptableObject("TagCloud", this);

No you can call the methods from JavaScript:

function addTags() {
    var control = document.getElementById("Xaml1");
    control.content.TagCloud.AddTag("Silverlight", "http://silverlight.net", 5);
    control.content.TagCloud.AddTag("Tagcloud", "http://blogs.tamtam.nl", 2);
    control.content.TagCloud.AddTag("Tam Tam", "http://www.tamtam.nl", 10);
    control.content.TagCloud.AddTag("Axelerate3D", "http://www.codeplex.com", 8);
    control.content.TagCloud.AddTag("WPF", "http://www.microsoft.com", 1);
    control.content.TagCloud.AddTag("SharePoint", "http://www.microsoft.com", 4);
    control.content.TagCloud.ProcessTags();
}

I’m just attaching some code to the onclick of a button and hard-coding the tags. Normally you would handle the onload of the document (or better yet the $(document).ready in jQuery) and get your tags from the Html to pass them to the Silverlight object.

And that wraps it up for this tutorial.