Building an AJAX web part with jQuery (Part 3)

3 comments

In part 1 of this series I explained a bit about the context and goal of creating an AJAX web part without using ASP.Net AJAX. I also showed the steps necessary for creating services that return data in the JSON format. In part 2 I showed you how to call these services from JavaScript and render the HTML for the data. In this last part I’ll show you how to use the jQuery UI and validation plugins.

jQuery UI

The jQuery UI plugin provides some useful widgets and effects to use in your jQuery based scripts. It also offers an advanced theme framework, so you don’t have to write all the css by yourself. You can use one of the included theme’s or roll your own with the ThemeRoller.

I’ve decided to use the UI plugin for tree things in my web part:

  • Datepicker widget to specify the orderdate
  • Dialog widget to show confirmation dialogs, edit forms and validation messages
  • Highlighting effect to focus the users attention to changing data, such as the shoppingcart

Datepicker

The datepicker enhances a standard text input box with a datapicker that slides out when the textbox receives focus. It contains different options for specifying the allowed dates, year/month selection and more. When it’s shown it will look like this:

image

Linking it to your input box is very simple. I use the following code:

$("#bpvorderdate").datepicker({
showOn: 'button',
minDate: +1, dateFormat: 'dd/mm/yy',
buttonImage: '/_layouts/images/calendar.gif',
buttonImageOnly: true
});

In this case, the user has to press a button (in this case an imagebutton) to open the datepicker.

Dialogs

Dialogs are a very useful way to give feedback to the user or asking for confirmation. In my web part I want to show a confirmation dialog when a user presses the delete icon next to a product in the shoppingcart or the “clear shoppingcart” button. The user will be presented with the following dialog:

image

Showing this is very easy. First we create a function that is called when the page is initialized:

function initializeDeleteItemDialog() {
var doOk= function() {
var paramsdata = {
"productId" : $("#bpvremoveitemid").val()
}
$.ajax({
type: "POST", url: "/_layouts/intranet2009/bpvshoppingcart.asmx/DeleteItem",
data: JSON.stringify(paramsdata),
contentType: "application/json;charset=utf-8",
dataType: "json",
success:rendershoppingcart,
error: showError
});

$("#bpvremoveitemdialog").dialog("close");
}
var doCancel = function()
{
$("#bpvremoveitemdialog").dialog("close");
}
var dialogOpts = {
modal: true,
buttons: {"Bewaren": doCancel, "Verwijderen": doOk},
autoOpen: false
}
$("#bpvremoveitemdialog").dialog(dialogOpts);
}

We first specify the code to execute when the user presses the Ok button. In this case we’ll call the DeleteItem method of the shoppingcart web service and then close the dialog. The Cancel button will close the dialog straight away. In the dialog options we specify the buttons with their callback. Then we hook up the dialog to the html element we want to show. The html is written out in the Render method of the web part:

writer.WriteLine(“<div id=\”bpvremoveitemdialog\” title=\”Product verwijderen?\”>”);
writer.WriteLine(“Weet u zeker dat u dit product uit uw winkelwagen wilt verwijderen?”);
writer.WriteLine(“<input type=\”hidden\” id=\”bpvremoveitemid\”/>”);
writer.WriteLine(“</div>”);

To open the dialog we just have to call the dialog method again with “open” as parameter:

function removeProduct(element) {
$("#bpvremoveitemid").val($(element).attr("productid") );
$("#bpvremoveitemdialog").dialog("open");
}

Validation

Validation of your inputs is supposed to be really easy with the validation plugin. Unfortunately this doesn’t count when you combine it with ASP.Net Webforms. With the validation plugin you attach the validation to a form within your html. Because ASP.Net Webforms uses one form tag for the entire page, this doesn’t allow you to set validation to a group of elements that would normally be contained within their own form tag. The solution I came up with for now only validates 1 element at a time.
If you now of a way to assign one validation and remove it again before assigning a new validation, let me know.

First we hook up all the validations we want on the form and we specify a custom validation
rule, called dutchDate:

$.validator.addMethod(
"dutchDate", function(value,element)
{ return value.match(/^\d\d?\/\d\d?\/\d\d\d\d$/);},
"Voer een datum in van het formaat dd/mm/yyyy" );
$("form").validate({
onsubmit: false,
onfocusout: false,
onkeyup: false,
onclick: false,
showErrors: showValidationError,
rules: {
bpvproductamount: {
required: true,
number: true
},
bpvproductid: {
required: true
},
bpvorderdate: {
required: true,
dutchDate: true
}
},
messages: {
bpvproductamount: {
required: "Aantal is een verplicht veld",
number: "Aantal moet een getal zijn"
},
bpvproductid: {
required: "U heeft geen product geselecteerd" },
bpvorderdate: {
required: "Bezorg-/ophaaldatum is een verplicht veld",
dutchDate: "Bezorg-/ophaaldatum moet in het formaat dd/mm/yyyy zijn"
}
}
});

I only want the validation to occur when I call it on specific elements from code, so we specify false on every event it normally triggers on. When there are errors,  I want to call a showValidationError function that shows the errors in a dialog box. Then we specify the rules and the messages we want to show when the rule isn’t matched. “bpvproductamount” equals the name attribute of the input element.

To call the validation we use the element method of the validation plugin:

if ($("form").validate().element("#txtbpvproductid") && $("form").validate().element("#txtbpvproductamount"))
{
// valid, so perfom actions
}

As soon as an element doesn’t pass validation, the method we attached to the showErrors event is called. Unfortunately this means only one error at a time will popup if multiple  elements don’t pass validation. To show the validation messages, we’ll make use of the Dialog widget once again:

function showValidationError(errorMap, errorList)
{
var message = "";
var i;
for(i=0; i < errorList.length; i++) {
message += errorList[i].message + "<br />";
}
if (message.length > 0) {
showMessage(message);
}
}

Conclusion

Building an AJAX web part with jQuery (and some plugins) can result in a very responsive UI with a good user experience. In the end, I don’t think building a web part with ASP.Net AJAX would have taken me less time as well. I’m not happy with the validation though. Although the jQuery validation plugin is very useful in most web frameworks (including ASP.Net MVC), it seems that it doesn’t combine well with web forms. But I haven’t been able to find a better plugin for it.

Tagged , , , , | 3 Comments

Building an AJAX web part with jQuery (Part 2)

1 comments

In part 1 of this series I explained a bit about the context and goal of creating an AJAX web part without using ASP.Net AJAX. I also showed the steps necessary for creating services that return data in the JSON format. In this post I’ll show you how to call these services from JavaScript and insert the data in the HTML placeholders rendered by the web part.

Calling the generic handler for products and categories

jQuery offers various methods to perform asynchronous calls to web resources. To retrieve JSON the most used are jQuery.ajax and jQuery.getJSON. The last one uses a HTTP GET request and is simpler to use, the jQuery.ajax method offers more options/flexibility. For retrieving the product categories and the products, I’ve decided to go with getJSON.
The code for retreiving the categories looks as follows:

function showProductCategories() {
$.getJSON(bpvweburl + "/_layouts/intranet2009/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 );
});
}

The first line is responsible for calling the handler. It specifies a inline callback method to handle the returned data. The JSON returned is processed by the PURE templating plugin. I’ve blogged about using this before.

Calling the shopping cart web service

For getting the data from the web service, we’ll use the jQuery.ajax method. That’s because we need to do a HTTP POST request as well as specify some other options for the request. For more information see this post by Dave Ward. To initially load the shopping cart we use the following code:

function loadShoppingcart()
{
$.ajax({
type: "POST",
url: "/_layouts/ecabointranet2009/bpvshoppingcart.asmx/GetItems",
data: "{}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: rendershoppingcart,
error: showError
});
}

function rendershoppingcart(msg) {
var cartcontainer = $("#bpvcartcontent");
cartcontainer.empty();

if (msg.d.length > 0)
{
var cartitemslist = cartcontainer.append($("#bpvcarttemplate").html());

var directives = {
'a.bpvedit[onclick]' : '"editAmount(this); return false;"',
'a.bpvremove[onclick]' : '"removeProduct(this); return false;"'
}
cartitemslist.autoRender( msg.d, directives );
}
else
{
cartcontainer.append("<span>U heeft nog geen producten in uw winkelwagen</span>");

}

$("#bpvcartcontent").effect("highlight", {color: "#ffcf57"}, 700, null);
}

function showError(xhr, status, error)
{
var err = eval("(" + xhr.responseText + ")");

$("#bpverrordialog span.errormessage").html(err.Message);

$("#bpverrordialog").dialog("open");
}

As you can see we post to the GetItems method of the asmx. We need to specify a empty JSON object as data (more information on that in this post, again by Dave Ward) and specify the contentType we want returned. When the AJAX call returns an error, we’ll call the showError method (which uses the jQuery UI dialog widget I’ll tell more about in part 3). When the call is successful, the rendershoppingcart method is called. This checks if the cart is empty, so we can display a message in that case, or uses PURE again for rendering the cart contents. To provide visual feedback to the user when the cart is updated, we’ll use the highlight effect on the cart to attract the attention of the user.
If we want to pass in parameters with an AJAX call (like the productId when we want to delete an item from the cart), we need to construct a parameters object and serialize that as string for usage in the call:

var paramsdata = { "productId" : $("#bpvremoveitemid").val() }

$.ajax({
type: "POST",
url: "/_layouts/ecabointranet2009/bpvshoppingcart.asmx/DeleteItem",
data: JSON.stringify(paramsdata),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: rendershoppingcart,
error: showError
});

As you can see, we use a JSON.stringify method for serializing the object as a string.

You can download the scriptfile needed for this from JSON.org.

All the other methods in the web service are called in the same way, so there’s no need to inundate you with more code on that. The only thing left is to show you parts of the contents of the Render method in the webpart:

// Templates
// Categorylist template
writer.WriteLine("<div id=\"bpvcategorytemplate\" style=\"display: none;\">");
writer.WriteLine("<h3>Productcategorie</h3>");
writer.WriteLine("<ul><li class=\"context\"><a href=\"#\" class=\"context context@category\">laden...</a></li></ul>");
writer.WriteLine("</div>");
// Productlist template
writer.WriteLine("<div id=\"bpvproducttemplate\" style=\"display: none;\">");
writer.WriteLine("<h3>Product</h3>");
writer.WriteLine("<ul><li class=\"context\"><a href=\"#\" class=\"Title ID@productid Description@description Code@productcode AttachmentUrl@imageurl\">geen items</a></li></ul>");
writer.WriteLine("</div>");
// Shoppingcart template
writer.WriteLine("<div id=\"bpvcarttemplate\" style=\"display: none;\">");
writer.WriteLine("<h3>Informatie en bestellen</h3>");
writer.WriteLine("<table><thead><tr><td class=\"ProductName\">Product</td><td>Aantal</td><td></td></tr></thead><tbody class=\"d\">");
writer.WriteLine("<tr class=\"context\"><td class=\"ProductName\">naam</td><td class=\"Amount\">aantal</td><td><a href=\"#\" class=\"bpvedit ProductID@productid Amount@amount\"><img src=\"/_layouts/images/ecabo/2009/page-edit.gif\" /></a> <a href=\"#\" class=\"bpvremove ProductID@productid\"><img src=\"/_layouts/images/ecabo/2009/bin.gif\" /></a></td></tr>");
writer.WriteLine("</tbody></table>");
writer.WriteLine("</div>");

// Item selector
writer.WriteLine("<div id=\"bpvitemselector\">");
writer.WriteLine("<h2>1. Selecteer uw producten</h2>");
writer.WriteLine("<div id=\"selector\">");
writer.WriteLine("<div id=\"bpvcategoriescontainer\"><h3>Productcategorie</h3></div>");
writer.WriteLine("<div id=\"bpvproductscontainer\"><h3>Product</h3></div>");
writer.WriteLine("</div>");
writer.WriteLine("<div id=\"bpvproductinfo\"><h3>Informatie en bestellen</h3>");
writer.WriteLine("<img src=\"/_layouts/images/blank.gif\" id=\"bpvproductimage\"/>");
writer.WriteLine("<span class=\"title\" id=\"bpvproducttitle\"></span>");
writer.WriteLine("<span class=\"description\"  id=\"bpvproductdescription\" /></span>");
writer.WriteLine("<label class=\"amount\">Aantal:</label><input type=\"text\" id=\"txtbpvproductamount\" name=\"bpvproductamount\"/>");
writer.WriteLine("<input type=\"hidden\" id=\"txtbpvproductid\" name=\"bpvproductid\"/>");
writer.WriteLine("<input type=\"hidden\" id=\"txtbpvproductcode\" />");
writer.WriteLine("<input type=\"button\" id=\"bpvaddproduct\" value=\"Voeg toe\"/>");
writer.WriteLine("</div>");
writer.WriteLine("</div>");

// Shoppingcart
writer.WriteLine("<div id=\"bpvshoppingcart\">");
writer.WriteLine("<h2>2. Lijst met uw bestelling</h2>");
writer.WriteLine("<div id=\"bpvcartcontent\"><span>U heeft nog geen producten in uw winkelwagen</span></div>");

writer.WriteLine("<a id=\"clearcart\" href=\"#\" onclick=\"clearCart(); return false;\">Alles verwijderen</a>");
writer.WriteLine("</div>");

As you can see, all we do is write out HTML. First I write out the HTML needed for the databinding of the categories and products (I removed that because it’s the same as the category one). Then some placeholders and form elements are rendered. There’s a little more HTML off course, but you get the point, NO CODE :-)

In the next part I’ll show you how to use some jQuery plugins to enhance the experience of the user and validation of the input.

Tagged , , , | 1 Comment

Building an AJAX web part with jQuery (Part 1)

5 comments

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):

image

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<bpvproduct> products = BPVProduct.GetAvailableProducts(category);

DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(List<bpvproduct>));
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<shoppingCartItem> ShoppingCart
{
get
{
if (HttpContext.Current.Session["BPVShoppingCart"] != null)
{
return (List<shoppingCartItem>)HttpContext.Current.Session["BPVShoppingCart"];
}
else
{
List<shoppingCartItem> shoppingCart = new List<shoppingCartItem>();
HttpContext.Current.Session.Add("BPVShoppingCart", shoppingCart);
return shoppingCart;
}
}
set
{
HttpContext.Current.Session["BPVShoppingCart"] = value;
}
}

[WebMethod(EnableSession = true)]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public List<shoppingCartItem> 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<shoppingCartItem> 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<shoppingCartItem> 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<shoppingCartItem> 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<shoppingCartItem> 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<shoppingCartItem> 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.

Tagged , , , | 5 Comments

jQuery performance optimization tips

1 comments

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.

Tagged | 1 Comment

Using a template plugin for jQuery to parse JSON data

1 comments

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.

Tagged , , | 1 Comment

MOSS Search and browser languages

0 comments

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.

Tagged , | Leave a comment

Creating a 3D tagcloud in Silverlight (part 2)

7 comments

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<Tag3D>.

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.

Tagged , , | 7 Comments

Adding AJAX.Net to your MOSS WebParts

0 comments

I know there are loads of posts on this subject already, but in this one I’ll try to give some useful tips on this subject.

First, you need to add AJAX.Net entries to your web.config of the MOSS site. A very easy way to do this is by using the Ajaxify stsadm extensions.

Simply add the included wsp to your solutions and deploy. After that you can easily add the entries by running the stsadm –addajaxmoss command. Unfortunately you need to add one entry manually, but that is given as part of the output when you run the command.

Next you need to add a ScriptManager to the pages with the AJAX enabled webparts. You can off course modify the masterpage. I chose to create a base web part that checks if a ScriptManager has been added to the page and adds it if necessary:

protected override void OnInit(EventArgs e)
{
base.OnInit(e);

//Register the ScriptManager

ScriptManager scriptManager = ScriptManager.GetCurrent(this.Page);

if (scriptManager == null)
{
scriptManager = new ScriptManager();
scriptManager.ID = “ScriptManager1″;

scriptManager.EnablePartialRendering = true;

this.Page.Form.Controls.AddAt(0, scriptManager);
}
}

Because MOSS needs a lot of JavaScript to function properly, this unfortunately requires another fix. You can read more about it here. I added the fix to the base webpart with the following code:

protected override void CreateChildControls()
{
//Add fix according to http://msdn2.microsoft.com/en-us/library/bb861877.aspx

EnsurePanelFix();
}

private void EnsurePanelFix()
{
if (this.Page.Form!= null)
{
String fixupScript = @”
_spBodyOnLoadFunctionNames.push(“”_initFormActionAjax”");
function _initFormActionAjax()
{
if (_spEscapedFormAction == document.forms[0].action)
{
document.forms[0]._initialAction = document.forms[0].action;
}
}

var RestoreToOriginalFormActionCore = RestoreToOriginalFormAction;
RestoreToOriginalFormAction = function()
{
if (_spOriginalFormAction != null)
{
RestoreToOriginalFormActionCore();
document.forms[0]._initialAction = document.forms[0].action;
}
}”;

ScriptManager.RegisterStartupScript(this, typeof(BaseWebPart), “UpdatePanelFixup”, fixupScript, true);
}
}

Because all the content you want to include in an UpdatePanel needs to be added to the ControlTemplateContainer.Controls collection, it’s not possible to write out any html you need between controls in the UpdatePanel by using the standard HtmlTextWriter.WriteLine method in the Render method. The easiest way is to add LiteralControls to the controlcollection of the UpdatePanel (with thanks to my collegue Wouter Lemaire for giving me the tip). To make this even easier, I’ve added the following extension method to my project:

public static void AddLiteral(this UpdatePanel updatePanel, string html)
{
Literal lit = new Literal();
lit.Text = html + “\r\n”;
updatePanel.ContentTemplateContainer.Controls.Add(lit);
}

You can call this method from the CreateChildControls method in your web part like so:

updatePanel.AddLiteral(“<table><tr>”);
updatePanel.AddLiteral(“<td></td><td>Ma</td><td>Di</td><td>Wo</td><td>Do</td><td>Vr</td></tr><tr>”);
updatePanel.AddLiteral(“<td>Ochtend</td><td>”);

To indicate progress you need some form of visual feedback to the user. For this you need to set the ProgressTemplate of the UpdatePanel. For this you need to create a class that implements the ITemplate interface.

Then you implement the InstantiateIn method to create a template:

public void ITemplate.InstantiateIn(Control container)
{
Label lbl = new Label();
lbl.Text = “Progress….”;
container.Controls.Add(lbl);
}

Then add an object of this class to the ProgressTemplate property of the UpdatePanel:

updateProgress.ProgressTemplate = new ProgressTemplate();
Tagged , , | Leave a comment

Creating a 3D tagcloud in Silverlight (part 1)

27 comments

When I saw the wp-cumulus plugin by Roy Tanck, I thought it would be a great idea to implement the same sort of functionality in Silverlight. It’s hardly original but allows me to learn some parts of the Silverlight framework.

The components behind it are quite simple:

  • Get (or send) the tags from your HTML page to the Silverlight usercontrol
  • Render the tags so it looks 3D
  • Create a method to rotate the tags based on the position of your mouse

Choosing a 3D library

The current version of Silverlight doesn’t include 3D functionality like WPF does through the Media3D namespace. Fortunately some developers implemented the same functionality in libraries for Silverlight. The main options I found were Kit3D and Axelerate3D. I decided to use the last one because that one mimics the RotateTransform3D class in WPF 3D the best (it contains a TryTransform method).

Rendering the tags

I decided to tackle the second item first, because if I wasn’t able to manage this, the other items wouldn’t be very useful.

To create a tag in 3D you need some basic functionality:

  • A way to store it’s x, y and z coordinates
  • A hyperlinkbutton to redirect to a page that shows all the items with that tag
  • A textblock to display the tag
public class Tag3D
{
public Tag3D(double x, double y, double z, string text)
{
centerPoint = new Point3D(x, y, z);
textBlock = new TextBlock();
textBlock.Text = text;
btnLink = new HyperlinkButton();
btnLink.Content = textBlock;
}

public HyperlinkButton btnLink { get; set; }

public TextBlock textBlock { get; set; }

public Point3D centerPoint { get; set; }

}

Then we need a way to make it look like it’s rendered in 3D. We do that by changing the fontsize and the opacity of the text. For that I created a method Redraw:

public void Redraw(double xOffset, double yOffset)
{
double posZ = centerPoint.Z + 200;
btnLink.FontSize = 10 * (posZ / 100);
double alpha = centerPoint.Z + 200;
if (alpha > 255)
alpha = 255;

if (alpha < 0)
alpha = 0;

btnLink.Foreground = new SolidColorBrush(Color.FromArgb(Convert.ToByte(alpha), 0, 0, ));
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));
}
Placing the tags

To distribute the tags evenly over the sphere, we need some math. Luckily someone was way ahead of me and posted a useful blogentry on this subject (this technique is also used in the wp-cumulus plugin).

The following method creates and places the tags in the canvas:

private void FillTags()
{
tagBlocks = new List<tag3D>();

string[] tags = new string[] { “Silverlight”,
“WPF”,
“3D”,
“Rotation”,
“SharePoint”,
“.Net”,
“C#”,
“Transform”,
“Blog”,
“TagCloud”,
“Tam Tam”,
“Axelerate3D”,
“MOSS”,
“Math”};

double radius = RootCanvas.Width / 3;
int max = tags.Length;
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 = new Tag3D(x, y, z, tags[i -1]);
tag.Redraw(RootCanvas.Width / 2, RootCanvas.Height / 2);
RootCanvas.Children.Add(tag.btnLink);
tagBlocks.Add(tag);
}
}

At the moment the tags to render are hard-coded but we’ll sort that out in part 2.

Rotating the tags

To rotate the tags we will use the position of the mouse as a starting point. When the mousepointer is in the center the tagcloud will remain in the current position. Once the mouse is further away from the centerpoint we’ll increase the rotationspeed. The location of the mousepointer compared to the centerpoint will set the angle of the rotation.

First we will set the rotation when the tagcloud loads:

void TagCloud_Loaded(object sender, RoutedEventArgs e)
{
FillTags();
rotateTransform = new RotateTransform3D();
rotateTransform.Rotation = new AxisAngleRotation3D(new Vector3D(1.0, 0.0, 0.0), 0);
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
LayoutRoot.MouseEnter += new MouseEventHandler(LayoutRoot_MouseEnter);
LayoutRoot.MouseLeave += new MouseEventHandler(LayoutRoot_MouseLeave);
}

Here we set the rotation angle to 0 and the rotationaxis to the x-axis. When the mouse moves, we’ll change those parameters, so the rotation will have an effect:

void LayoutRoot_MouseMove(object sender, MouseEventArgs e)
{
Point mouseLocation = e.GetPosition(RootCanvas);
double relativeX = mouseLocation.X – (RootCanvas.ActualWidth / 2);
double relativeY = mouseLocation.Y – (RootCanvas.ActualHeight / 2);
MouseX.Text = relativeX.ToString();
MouseY.Text = relativeY.ToString();
double speed = Math.Sqrt(Math.Pow(relativeX, 2) + Math.Pow(relativeY, 2)) / 170;
RotationSpeed.Text = speed.ToString();
rotateTransform.Rotation = new AxisAngleRotation3D(new Vector3D(relativeY, relativeX, 0), speed);
}

To trigger the movement, we have to capture the MouseEnter and MouseLeave events:

void LayoutRoot_MouseLeave(object sender, MouseEventArgs e)
{
     LayoutRoot.MouseMove -= LayoutRoot_MouseMove;
     runRotation = false;
}

void LayoutRoot_MouseEnter(object sender, MouseEventArgs e)
{
     LayoutRoot.MouseMove += new MouseEventHandler(LayoutRoot_MouseMove);
     runRotation = true;
}

Now that the rotationparameters are set we need to rotate the tags, or more precisely the centerpoint of the tag. To accomplish this we’ll make use of the Rendering event of the CompositionTarget object. This is called everytime the Silverlight plugin wants to render a new frame.

void CompositionTarget_Rendering(object sender, EventArgs e)
{
    if (runRotation)
    {
        if (((AxisAngleRotation3D)rotateTransform.Rotation).Angle > 0.05)
        RotateBlocks();
    }
}

private void RotateBlocks()
{
foreach (Tag3D textBlock in tagBlocks)
{
Point3D newPoint;
if (rotateTransform.TryTransform(textBlock.centerPoint, out newPoint))
{
textBlock.centerPoint = newPoint;
textBlock.Redraw(RootCanvas.ActualWidth / 2, RootCanvas.ActualHeight / 2);
}
}
}

To relieve the CPU a bit, we’ll only rotate the tags if the rotation angle is higher than a threshold value. The actual transformation is accomplished by invoking the TryTransform method and passing it the current centerpoint of each tag.

At the moment the Silverlight control looks like this:

In the next part I’ll show you a way to dynamically set the tags, base their fontsize on the actual weight of the tag and actually use the hyperlink button.

Tagged , | 27 Comments

Hosting your Silverlight application and media in the cloud

0 comments

Update: The Silverlight Streaming beta has ended. If you want to host your Silverlight apps on Microsoft’s infrastructure and video’s check out Windows Azure

Microsoft now offers a service called Silverlight Streaming for hosting your Silverlight content in the cloud. At the moment the service is in beta and you get a whopping 10 GB of storagespace and 5 TB of bandwith per user account per month.

Embedding your application in a page can be done by inserting an iframe:

[Removed because of ended beta]

Tagged , | Leave a comment