AJAX

How to create a complete AJAX Server Control. Part 6.

Posted on Updated on

This is the last (planned) post for the series is related to using the control inside an update panel.

You can access the previous ones here:

When you put a control inside an UpdatePannel (with UpdateMode = “Conditional” ) what it happens is that the de AJAX Framework will call the implementation of the methods in the interface Sys.IDisposable to release the resources before destroying everything inside it using the dispose() method. And once the result is returned from the server, the control gets constructed and initialised again with the initialize() method.

It’s important to do that to avoid side effects and help the system with the garbage collection and avoid memory leaks.

ShoppingCart.js

// As the control implements Sys.IDisposable, the MS Ajax Framework requires of this method.
$ShoppingCart.prototype.initialize = function()
{
	$ShoppingCart.callBaseMethod(this, "initialize");
	this._addHandlers();
}
// As the control implements Sys.IDisposable, the MS Ajax Framework requires of this method.
// This method will be use release used resources (specially required to work with Update Panels)
$ShoppingCart.prototype.dispose = function()
{
	$ShoppingCart.callBaseMethod(this, 'dispose');
	this._clearHandlers();
}
$ShoppingCart.prototype._addHandlers = function()
{
	$addHandlers(this._cartImage, {
		dblclick: Function.createDelegate(this, this._cartDoubleClick)
	});
	$addHandlers(this._infoImage, {
		click: Function.createDelegate(this, this._infoImageClick)
	});
	$addHandlers(this._closeItemsImage, {
		click: Function.createDelegate(this, this._closeItemsImageClick)
	});
}
$ShoppingCart.prototype._clearHandlers = function()
{
	// remove event handlers to avoid memory leaks
	$clearHandlers(this._cartImage);
	$clearHandlers(this._infoImage);
	$clearHandlers(this._closeItemsImage);
}

Once these methods are correctly called, we can put our control inside the UpdatePanel (UpdateMode=”Conditional”). But before doing that, let’s check what’s going behind the scenes with the full postback.

To do that enable the Web Development Helper bar (it’s included in IE8 and you can download it for IE7).

image

Then enable logging and click on the Add item button.

image

Well, these are the calls to the server and the size of the downloaded elements (you can double click in each of them to see what’s what).

Now let’s put the control in the UpdatePanel, restart the page and enable logging one more time.

ShoppingCart.aspx

        
            
                
            
        

It seems that nothing happened because only the shopping cart control was updated. Check how the amount of data received has been reduces dramatically, 3 connections instead of 7 and the page connection reduced from 10Kb to 6Kb. If we open the received data we can see how the creation of the control was sent from the server along with the html representation as I had explained before.

If you have any problems with JS running inside Update Panels and Postbacks, this is the tool you can use to check what was sent and returned.

image

Well, I hope that you learnt few things about AJAX Server controls and the MS AJAX Framework. A good way to keep learning about it is downloading the source code for the AjaxControlToolkit and have a look at the samples and controls, believe me, there’s plenty there.

You can download the code here.
Important: I couldn’t upload the file as it was a zip file, so I added the extension jpg. After saving the file, remove the extension and unzip it normally.

How to create a complete AJAX Server Control. Part 5.

Posted on Updated on

In the previous post we saw that the Items were disappeared like cowards once we executed a postback. That’s the standard behaviour of the GridView control as well, so don’t worry.

To maintain the Items we have several choices:

  1. The basic implementation is to keep the list in the view state (that’s the implementation in the samples). This will work with no issues but it’s not very configurable
  2. 		List items
    		{
    			get { return GetPropertyValue("items", (List)null); }
    			set { SetPropertyValue("items", value); }
    		}
    
  3. A bit more sofisticated solution, and the way GridView works, would be taking into consideration the EnableViewState property and depending on the value, then saving it in the View State or in a member
  4. 		List _items = new List();
    		List items
    		{
    			get
    			{
    				if (EnableViewState)
    					return GetPropertyValue("items", (List)null);
    				else
    					return _items;
    			}
    			set
    			{
    				if( value == null)
    					value = new List();
    
    				if (EnableViewState)
    					SetPropertyValue("items", value);
    				else
    					_items = value;
    			}
    		}
    

If we run the example now, we can see how all the items are there after the postback and the new ones are added to the list, just as expected. Just remember that the more you add to the ViewState, de bigger the page.

You can download the code here.
Important: I couldn’t upload the file as it was a zip file, so I added the extension jpg. After saving the file, remove the extension and unzip it normally.

How to create a complete AJAX Server Control. Part 4.

Posted on Updated on

In this post I’m going to add functionality in the client side that will be executed in the server side (for previous posts of the series please refer to part 1, part 2 and part 3).

To achieve this, there are 2 interfaces that have been defined:

  • System.Web.UI.IPostBackEventHandler: used to implement postbacks
  • System.Web.UI.ICallbackEventHandler: used to implement callbacks

In the sample I’ll be implementing both of them; however the one that we are going to find more useful will be the one used for the postbacks, as it involves a change of state in the server control. Callbacks can be useful if we want to run something in the server and get a value as response but doesn’t require changes in the server side of control.

Before implementing the interfaces we need to define the events that we want to implement in the server side. To do that I have defined the ServerEvent enum with the 2 events that I want to capture in the JS and run in the server, DoubleClick and ItemAdded (I have also created 2 delegates for the events to show how to fully customise everything; ClickEventHandler and ItemAddedEventHandler). And then 2 methods to raise the events, OnDoubleClick and OnItemAdded.

So far, so good, but the client side needs to know which events have been hooked in the server side so that we only call the server side if it’s required. RunAtServerEvents checks which events have to be raised and sends to the client the list of events (a string with events separated by “;” and implemented as a client side property). Then we have the property AutoPostBack so we can decide if we want to execute them as postbacks or callbacks (for simplicity I’ll be assuming all as postbacks or all as callbacks but we could customise for each of them in the JS code). And finally we need the property CallbackID, required to implement the callbacks and postbacks (initialised to UniqueID).

Something interesting to notice is that on OnPreRender there is a piece of code that I found somewhere in the code of one of the AjaxControlToolkit controls, it seems that we need to add a call to Page.ClientScript.GetCallbackEventReference(this, “”, “”, “”) to make the callbacks work (required code to register callbacks in the client).

ShoppingCart.cs

	/// 
	/// Click delegate definition
	/// 
	/// 
	public delegate void ClickEventHandler(object sender);

	/// 
	/// Item added delegate
	/// 
	/// 
	/// new item
	public delegate void ItemAddedEventHandler(object sender, ShoppingCartItem addedItem);

	public class ShoppingCart : ScriptControlBase
	{

		/// 
		/// Server events raised from the client
		/// 
		enum ServerEvent
		{
			DoubleClick,
			ItemAdded
		}

		//...

		protected override void OnPreRender(EventArgs e)
		{
			if (!this.DesignMode)
			{
				if (ScriptManager.GetCurrent(Page) == null)
				{
					throw new HttpException("A ScriptManager control must exist on the current page.");
				}
				InitialiseControls();

				CreateHTMLItems();

				CallbackID = UniqueID; // needed for the callbacks and postbacks

				// Create JavaScript function for ClientCallBack WebForm_DoCallBack
				// Not sure why we need it, but the callback doesn't get registered on the client side properly without it.
				Page.ClientScript.GetCallbackEventReference(this, "", "", "");

			}

			base.OnPreRender(e);
		}

		#region Server Events
		/// 
		/// If we want to execute server events with a postback.
		/// True: A full postback happens and the page will be refreshed.
		/// Use UpdatePanel control to refresh certain areas.
		/// False: The events will run on the server with no postback (it'll use a Callback).
		/// If any data is updated a manual UI refresh needs to be forced (full page or UpdatePanel).
		/// 
		[Description("If we want to execute server events with a postback.")]
		[ExtenderControlProperty]
		public bool AutoPostBack
		{
			[DebuggerStepThrough]
			get { return GetPropertyValue("AutoPostback", true); }
			[DebuggerStepThrough]
			set { SetPropertyValue("AutoPostback", value); }
		}

		/// 
		/// Control ID to be used when doing callbacks from the client
		/// 
		[ExtenderControlProperty]
		//[ClientPropertyName("CallbackID")]
		[Browsable(false)]
		public string CallbackID
		{
			[DebuggerStepThrough]
			get { return GetPropertyValue("CallbackID", UniqueID); }
			[DebuggerStepThrough]
			set { SetPropertyValue("CallbackID", value); }
		}

		/// 
		/// Events that will be run at server
		/// 
		[Browsable(false)]
		[ExtenderControlProperty]
		[ClientPropertyName("runAtServerEvents")]
		public string RunAtServerEvents
		{
			get
			{
				string runAtServerEvents = "";

				if (DoubleClick != null) runAtServerEvents += "DoubleClick;";
				if (ItemAdded != null) runAtServerEvents += "ItemAdded;";

				if (runAtServerEvents.Length > 0)
					return runAtServerEvents.Substring(0, runAtServerEvents.Length - 1);
				else
					return "";
			}
			set { }
		}

		/// 
		/// Event raised when the a cell is double clicked
		/// 
		[Description("Event raised when the a cell is double clicked.")]
		public event ClickEventHandler DoubleClick;

		/// 
		/// Raise DoubleClick event
		/// 
		/// 
		protected virtual void OnDoubleClick()
		{
			if (DoubleClick != null)
			{
				DoubleClick(this);
			}
		}

		/// 
		/// Event raised when the a cell is double clicked
		/// 
		[Description("Event raised when the a cell is double clicked.")]
		public event ItemAddedEventHandler ItemAdded;

		/// 
		/// Raise DoubleClick event
		/// 
		/// 
		protected virtual void OnItemAdded(ShoppingCartItem item)
		{
			if (ItemAdded != null)
			{
				ItemAdded(this, item);
			}
		}
		#endregion
	}

The next step is the implementation of the interfaces code that will be called by the AJAX framework, IPostBackEventHandler and ICallbackEventHandler. To keep it simple I’ll implement the same code in both.

The method that does the work is ExecuteServerEvent, in where the parameter coming from the client will define the event name and the parameters (I’ve kept it simple and I’ve used “eventname;jsonParameters”). Double click doesn’t receive any parameters, but ItemAdded receives as a parameter the new item that has been added to the shopping cart. From there, all is standard C#.

ShoppingCart.cs

	public class ShoppingCart : ScriptControlBase, IPostBackEventHandler, ICallbackEventHandler
	{

		//...
		#region Server Events

		//...

		/// 
		/// Executes the server event
		/// 
		/// 
		/// Event Parameters created in the client that follows this structure.
		/// EventName;JSON object serialised
		/// i.e:
		///		ItemAdded;JSON serialised
		/// 
		/// 
		private string ExecuteServerEvent(string eventFromClient)
		{
			// Get the event name
			int separator = eventFromClient.IndexOf(";");
			string serverEvent = eventFromClient.Substring(0, separator);
			ServerEvent e = (ServerEvent)Enum.Parse(typeof(ServerEvent), serverEvent);

			// Get the event args (JSON serialised string)
			string eventArgs = eventFromClient.Substring(separator + 1);

			switch (e)
			{
				case ServerEvent.DoubleClick:
					OnDoubleClick();
					break;
				case ServerEvent.ItemAdded:
					ShoppingCartItem addedItem = new JavaScriptSerializer().Deserialize(eventArgs);
					AddItem(addedItem);
					break;
				default:
					throw new Exception("Wrong event!");
					break;
			}
			return "";
		}

		/// 
		/// Adds an item
		/// 
		/// 
		private void AddItem(ShoppingCartItem addedItem)
		{
			Items.Add(addedItem);

			OnItemAdded(addedItem);
		}
		#endregion 

		#region IPostBackEventHandler Members

		/// 
		/// Raise postback event in the server.
		/// 
		/// argument from JScript
		public void RaisePostBackEvent(string eventArgument)
		{
			ExecuteServerEvent(eventArgument);
		}
		#endregion

		#region ICallbackEventHandler Members
		/// 
		/// Return value for the callback call
		/// 
		string callbackResult = "";

		/// 
		/// Returns the value generated by the event (if any).
		/// 
		/// 
		string ICallbackEventHandler.GetCallbackResult()
		{
			return callbackResult;
		}

		/// 
		/// Raises the server event in the server.
		/// 
		/// argument from JScript
		void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
		{
			callbackResult = ExecuteServerEvent(eventArgument);
		}
		#endregion
	}

In the client side we need to add some code to raise the events (and implement JS properties). The method that will call the server is _raiseServerEvent.

  1. It checks the the event that we want to fire has been hooked up in the server side
  2. It creates the parameters with the event name to call and the parameters required for that event
  3. Depending if we want to execute a postback or a callback it calls the server in a different way.
  4. As Callbacks run asynchronously, we need to provide the methods to execute in case it was successful and if there was an error.

ShoppingCart.js

// -------------------------------------- Client side Properties --------------------------------------
$ShoppingCart.prototype.get_items = function() { return this._items; }
$ShoppingCart.prototype.set_items = function(value)
{
	this._items = Sys.Serialization.JavaScriptSerializer.deserialize(value);
}

$ShoppingCart.prototype.get_CallbackID = function() { return this._CallbackID; }
$ShoppingCart.prototype.set_CallbackID = function(value) { this._CallbackID = value; }

$ShoppingCart.prototype.get_AutoPostBack = function() { return this._AutoPostBack; }
$ShoppingCart.prototype.set_AutoPostBack = function(value) { this._AutoPostBack = value; }

// -------------------------------------- Server Side Events --------------------------------------
$ShoppingCart.prototype.get_runAtServerEvents = function()
{
	return this._runAtServerEvents;
}

$ShoppingCart.prototype.set_runAtServerEvents = function(value)
{
	this._runAtServerEvents = value.split(';');
}

$ShoppingCart.prototype._raiseServerEvent = function(eventName, eventArgs)
{
	if (!Array.contains(this._runAtServerEvents, eventName))	return;

	var eventArgsSerialised = Sys.Serialization.JavaScriptSerializer.serialize(eventArgs);
	var args = String.format("{0};{1}", eventName, eventArgsSerialised);
	var id = this._CallbackID;

	if (this._AutoPostBack)
	{// calls IPostBackEventHandler.RaisePostBackEvent in the server control
		__doPostBack(id, args);
	}
	else
	{
		// calls ICallbackEventHandler.RaiseCallbackEvent
		// the return value from ICallbackEventHandler.GetCallbackResult() is sent
		// to the callback handler this._receiveServerData
		WebForm_DoCallback(id, args, this._receiveServerData, this, this._onCallbackError, true)
	}
}

// Handler for successful return from callback
$ShoppingCart.prototype._receiveServerData = function(arg, context)
{
	// context is the instance of the class (this)

	// update the UI or Raise an event to inform that we finished the callback
	alert("Back from the callback!");
	//context.updateUI();
	//context.raiseEndClientCallback(arg);
}

// Error handler for the callback
$ShoppingCart.prototype._onCallbackError = function(message, context)
{
	alert(String.format(AjaxControlToolkit.Resources.Rating_CallbackError, message));
}

// -------------------------------------- Control methods --------------------------------------
$ShoppingCart.prototype.addItem = function(itemText, itemDescription, itemQuantity, itemTotalPrice)
{
	var newItem = new $ShoppingCartItem(itemText, itemDescription, itemQuantity, itemTotalPrice);

	this._raiseServerEvent("ItemAdded", newItem);
}

// Classes to handle event arguments and data transfer to the server ---------------------------------
var $ShoppingCartItem = AjaxControl.ShoppingCartItem = function(itemText, itemDescription, itemQuantity, itemTotalPrice)
{
	this.Text = itemText;
	this.Description = itemDescription;
	this.Quantity = itemQuantity;
	this.TotalPrice = itemTotalPrice;
}

To test it, let’s add some code to our web page. I have added 4 text boxes to introduce the values and and a button that will add the new item. Additionally I added a GridView control that is Databinded with the items in the shopping cart (server event of ItemAdded).

ShoppingCart.aspx


    

        function addNewItem() 
        {
            $find("Cart1").addItem(
                    $get("newItemText").value,
                    $get("newItemDescription").value,
                    $get("newItemQuantity").value,
                    $get("newItemTotalPrice").value
                    );
        
        }
    



    
    
    
    <div>
        <table>
            <tr>
                <td>Text</td><td></td>
            </tr>
            <tr>
                <td>Description</td><td></td>
            </tr>
            <tr>
                <td>Quantity</td><td></td>
            </tr>
            <tr>
                <td>TotalPrice</td><td></td>
            </tr>
            <tr>
                <td colspan="2" align="right"></td>
            </tr>
         </table>
    </div>
    <div>
        

        
    </div>
    

ShoppingCart.aspx.cs

	public partial class ShoppingCart : System.Web.UI.Page
	{
		protected void Page_Load(object sender, EventArgs e)
		{
			if(! IsPostBack)
			{
				var items = new List
				{
					//...
				};

				Cart1.Items = items;

				GridView1.DataSource = Cart1.Items;
				GridView1.DataBind();
			}
		}

		protected void Cart1_ItemAdded(object sender, ShoppingCartItem addedItem)
		{
			GridView1.DataSource = Cart1.Items;
			GridView1.DataBind();
		}
	}

As you will notice the old items are lost after the postback and only the latest item is rendered. This is the expected behaviour (Gridview implementation behaves the same). Next post I’ll show how to make the control remember the previous items.

Well, that’s all. I know that there’s a lot to digest here but believe me, if you had to figure it out by yourself it would take a lot longer.

You can download the code here.
Important: I couldn’t upload the file as it was a zip file, so I added the extension jpg. After saving the file, remove the extension and unzip it normally.

How to create a complete AJAX Server Control. Part 3.

Posted on Updated on

In the previous posts we have created the server control and JS representation in the client. We have also added functionality to add new items in the server and add a client event to display and hide the items in the shopping cart (see part 1 and part 2).

The next step in the development of the server control will be to enable the client side to access the data stored in the control and fire client events that could be handled in the web page.

You may probably think that from the client side you can access to the items stored in the cart by inspecting the DOM and using JQuery, for instance. True. But it’s not the natural way of doing it and it gives you very little margin if you want to change the html that is rendered because then, you should have to go and update all the code that reads from the control.

To implement this functionality I’m going to define some helper methods in the class to interact easily with the Viewstate an avoid casting in the code.

ShoppingCart.cs

 
	public class ShoppingCart : ScriptControlBase
	{
		//...
		#region Helper methods
		/// 
		/// Retrieves the property value from the ViewState
		/// 
		/// Type of value
		/// Property to retrieve
		/// value to be used in case it's not initialised.
		/// 
		[DebuggerStepThrough]
		protected V GetPropertyValue(string propertyName, V nullValue)
		{
			if (ViewState[propertyName] == null)
			{
				return nullValue;
			}
			return (V)ViewState[propertyName];
		}

		/// 
		/// Save the property value in the ViewState
		/// 
		/// Property type
		/// Property name
		/// Value
		[DebuggerStepThrough]
		protected void SetPropertyValue(string propertyName, V value)
		{
			ViewState[propertyName] = value;
		}
		#endregion
		//...
	}

Once we have these methods we can start adding the real juicy stuff.

First of all we need to add a namespace to get the JS serialisation classes to be able to serialise and de-serialise JSON objects.

Now to add a client event (I’ve chosen Double Click on the Cart image) we need to add a property in where we will set the name of the JS function that we need to call for the event. In the code below you can see the property OnClientCartDoubleClick. Notice all the attributes added to the property:

  • CategoryAttribute and Description are the standard attributes to get the nice integration with the properties window in Visual Studio
  • ExtenderControlEvent is used to say that this property is used to define a control event in the client (it’s declared in the AjaxControlToolkit and used via reflexion at runtime)
  • ClientPropertyName(“cartDoubleClick”) helps us to change the name of the event in the client (by default uses the property name unless it’s renamed using this attribute)

I’m also going to add a property in the client called items that will contain the items in the cart the same way they are available in the server so that you can read then in the web page. To do that, the only difference from the event is the attribute that we need to use to do that, now we’ll use ExtenderControlProperty instead of ExtenderControlEvent. To send the items to the client I’m using JSON serialisation so they can be easily serialised to string and they will be deserialised in the client side.

ShoppingCart.cs

	using System.Web.Script.Serialization;

	public class ShoppingCart : ScriptControlBase
	{
		//...
		#region Client Events
		/// 
		/// Client function that will be executed for CartDoubleClick
		/// 
		[CategoryAttribute("Client Events")]
		[Description("Client function that will be executed for OnClientCartDoubleClick.")]
		[ExtenderControlEvent]
		[ClientPropertyName("cartDoubleClick")]
		public string OnClientCartDoubleClick
		{
			[DebuggerStepThrough]
			get { return GetPropertyValue("OnClientCellDoubleClick", (string)null); }
			[DebuggerStepThrough]
			set { SetPropertyValue("OnClientCellDoubleClick", value); }
		}

		#endregion

		#region Client Properties
		/// 
		/// Client property
		/// 
		[Browsable(false)]
		[ExtenderControlProperty]
		[ClientPropertyName("items")]
		public string ClientItems
		{
			get
			{
				JavaScriptSerializer jser = new JavaScriptSerializer();
				string jsonItems = jser.Serialize(items);
				return jsonItems;
			}
			set { }
		}
		#endregion
		//...
	}

To implement the client side is even easier.

Event (cartDoubleClick):

  1. We have to add the methods to hook and unhook the event in the class
    • add_cartDoubleClick: Convention used by MS AJAX Framework to hook (add_ + “eventname”)
    • remove_cartDoubleClick: Convention used by MS AJAX Framework to unhook (remove_ + “eventname”)
  2. Now we can add a handler to handle the double click event of the image ( _addHandlers and _clearHandlers )
  3. In the section HTML Events of the JS class I have created a method to call the client event (_onCartDoubleClick) in where I check if the event is hooked, I call the function that was fetined in the server side

Property (items):

  1. We have to add the methods for the get/set in the class
    • get_items: Convention used by MS AJAX Framework for the get (get_ + “propertyname”)
    • set_items: Convention used by MS AJAX Framework for the set (set_ + “propertyname”)
  2. In the set property I deserialise the items into objects and assign the array to the _items property (remember that as a convention it’s used underscore to express the meaning of private in a class)

ShoppingCart.js

$ShoppingCart.prototype._addHandlers = function()
{
	$addHandlers(this._cartImage, {
		dblclick: Function.createDelegate(this, this._cartDoubleClick)
	});

	//...
}

$ShoppingCart.prototype._clearHandlers = function()
{
	// remove event handlers to avoid memory leaks
	$clearHandlers(this._cartImage);
	//...
}

// ---------------------- HTML Events -----------------------

$ShoppingCart.prototype._infoImageClick = function(e)
{
	this._itemsDiv.style.display = "block";
}

$ShoppingCart.prototype._closeItemsImageClick = function(e)
{
	this._itemsDiv.style.display = "none";
}

$ShoppingCart.prototype._cartDoubleClick = function(e)
{
	e.stopPropagation();

	// raise the client event
	this._onCartDoubleClick(e);
}
// --------------------- Client side Events -------------
// defined in the server side as:
//		[ExtenderControlEvent]
//		[ClientPropertyName("cartDoubleClick")]

$ShoppingCart.prototype.add_cartDoubleClick = function(handler)
{
	this.get_events().addHandler("cartDoubleClick", handler);
}
$ShoppingCart.prototype.remove_cartDoubleClick = function(handler)
{
	this.get_events().removeHandler("cartDoubleClick", handler);
}
$ShoppingCart.prototype._onCartDoubleClick = function(e)
{
	if (!this._events) return;
	var handler = this._events.getHandler("cartDoubleClick");
	if (handler) handler(e);
}

// ---------------------- Client side Properties ----------------------------
$ShoppingCart.prototype.get_items = function() { return this._items; }
$ShoppingCart.prototype.set_items = function(value)
{
	this._items = Sys.Serialization.JavaScriptSerializer.deserialize(value);
}

To use the newly created event and property I have defined a JS function in the web form that enumerated the items in the control and displays a message box when the cart image is double clicked.

ShoppingCart.aspx


    
    
        function onCartDoubleClickInTheClient(e)
        {
            // we use $find to retrieve an AJAX component ( $get to retrieve the HTML element )
            var cart = $find("Cart1");

            var items = cart.get_items();
            var itemsStr = "";
            for (var i = 0; i &lt; items.length; i++)
            {
                itemsStr += &quot;\n  &quot; + i + &quot; - &quot; + items[i].Text;
            }

            alert(&quot;Double Click! : &quot; + itemsStr);          
        }
        
    


    
    
    
    <div>
        
    </div>
    

If you view source in the browser you can see how the new control has been created passing all the added elements.

Sys.Application.initialize();
Sys.Application.add_init(function() {
    $create(
    AjaxControl.ShoppingCart, 
    {"items":"[{\"DateAdded\":\"\\/Date(1245633460294)\\/\",\"Text\":\"Learn Ajax in 1h!\",\"Description\":\"If you want to learn Ajax in 1h, this is your book.\",\"Quantity\":1,\"TotalPrice\":28.9},{\"DateAdded\":\"\\/Date(1245633460295)\\/\",\"Text\":\"Ajax for Dummies!\",\"Description\":\"If you think that you are hopeless, this is your book.\",\"Quantity\":2,\"TotalPrice\":70},{\"DateAdded\":\"\\/Date(1245633460295)\\/\",\"Text\":\"Advanced Ajax Server Controls\",\"Description\":\"When you want to go further, this is your book.\",\"Quantity\":1,\"TotalPrice\":86.5}]"}, 
    {"cartDoubleClick":onCartDoubleClickInTheClient}, 
    null, 
    $get("Cart1"));
});

We are closer to have all the functionality ready to be used.

You can download the code here.

Important: I couldn’t upload the file as it was a zip file, so I added the extension jpg. After saving the file, remove the extension and unzip it normally.

How to create a complete AJAX Server Control. Part 2.

Posted on Updated on

In this post I’m going to continue with the sample of the part 1.

Now it’s time to start adding functionality to the control.

First of all I’m going to add Items to the control.

To do that I have declared a class that contains the data that I wanted to show. I could have defined an interface and then implement it in a class out of the project, but for this presentation I’ve kept it simple.

ShoppingCartItem.cs

public class ShoppingCartItem
	{
		public DateTime DateAdded { get; set; }
		public string Text { get; set; }
		public string Description { get; set; }
		public int Quantity { get; set; }
		public float TotalPrice { get; set; }
	}

And now, I’ll define in the control class a collection property in where I’ll store all the items in the shopping cart.

I’ll call the property Items.

I’ve also added code to render some HTML for the items (<table><tr><td> style). And to show how to add client side functionality, I put it inside a non visible div that will be made visible in JS when we click on the cart.

The HTML elements are created in InitialiseControls(), CreateTableItemsHeader() and CreateHTMLItems() functions.

ShoppingCart.cs

	public class ShoppingCart : ScriptControlBase
	{
		List items = new List();

		/// 
		/// Div control used as a pop-up container to display the selected items
		/// 
		HtmlGenericControl itemsDiv;

		/// 
		/// Table where the items are rendered
		/// 
		HtmlTable tableItems;

		/// 
		/// Shopping Cart Items
		/// 
		public List Items{get{return items;}
			set
			{
				if (value == null) items = new List();
				else items = value;
			}
		}

		public ShoppingCart(): base(false, HtmlTextWriterTag.Div)
		{
		}

		#region Control Rendering

		protected override void OnPreRender(EventArgs e)
		{
			if (!this.DesignMode)
			{
				if (ScriptManager.GetCurrent(Page) == null)
				{
					throw new HttpException("ScriptManager control must.");
				}
				InitialiseControls();

				CreateHTMLItems();
			}

			base.OnPreRender(e);
		}

		private void InitialiseControls()
		{
			Image cartImage = new Image();
			cartImage.ID = "cartImage";
			cartImage.Attributes["class"] = "cartImage";
			cartImage.ToolTip = "Shopping Cart";
			cartImage.ImageUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(),"AjaxControl.Images.cart.png");
			this.Controls.Add(cartImage);

			itemsDiv = new HtmlGenericControl("div");
			itemsDiv.ID = "itemsDiv";
			itemsDiv.Attributes["class"] = "itemsDiv";
			itemsDiv.Style.Add(HtmlTextWriterStyle.Display, "none");
			this.Controls.Add(itemsDiv);

			tableItems = new HtmlTable();
			tableItems.Attributes["class"] = "itemsTable";
			itemsDiv.Controls.Add(tableItems);

		}

		/// 
		/// Creates the items to be rendered
		/// 
		public void CreateHTMLItems()
		{
			tableItems.Rows.Clear();

			CreateTableItemsHeader();

			// Add data rows
			foreach (var item in items.OrderBy(i => i.DateAdded))
			{
				HtmlTableRow rowItem = new HtmlTableRow();
				rowItem.Attributes["class"] = "itemRow";
				tableItems.Rows.Add(rowItem);

				HtmlTableCell cellText = new HtmlTableCell();
				//...
				rowItem.Cells.Add(cellText);
				//...
			}
		}

		/// 
		/// Creates a heading to close the table
		/// 
		private void CreateTableItemsHeader()
		{
			//...

			Image closeImage = new Image();
			closeImage.ID = "closeItemsImage";
			closeImage.ImageUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(), "AjaxControl.Images.cancel.png");
			//...
			cellHead.Controls.Add(closeImage);
		}

		#endregion

	}

Once we have the server side of the control ready we can start with the client side.

In the JS class I have added some event handling. I first get the html elements that represent the cart (to add a onclick event), then the items div (to make the items visible) and finally the close image to hide the items again.

The method _addHandlers() will hook up all the events and the method _clearHandlers() will remove them.

Important: Something to notice is that in JS we don’t have the concept of delegates and if we add a handler using the standard method, then inside the method we won’t have access to the current object, this. In the MS AJAX Framework, they have implemented the concept using Function.createDelegate(this, this._cartImageClick), in there the firs parameter is the instance of the object that we want to access as this inside the event handler and the second is the handler. Now we can have access to all the methods and members of the class inside the handler.

ShoppingCart.js

var $ShoppingCart = AjaxControl.ShoppingCart = function(element)
{
	this._element = element; // div control that contains everything

	AjaxControl.ShoppingCart.initializeBase(this, [element]);

	this._cartImage = $get(element.id + "_cartImage");
	this._itemsDiv = $get(element.id + "_itemsDiv");
	this._closeItemsImage = $get(element.id + "_closeItemsImage"); 

}

// ------------------- Control Initialisation and Destruction ------------------------
$ShoppingCart.prototype.initialize = function()
{
	$ShoppingCart.callBaseMethod(this, "initialize");

	this._addHandlers();
}

// As the control implements Sys.IDisposable, the MS Ajax Framework requires of this method.
// This method will be use release used resources (specially required to work with Update Panels)
$ShoppingCart.prototype.dispose = function()
{
	$ShoppingCart.callBaseMethod(this, 'dispose');
}

$ShoppingCart.prototype._addHandlers = function()
{
	$addHandlers(this._cartImage, {
		click: Function.createDelegate(this, this._cartImageClick)
	});
	$addHandlers(this._closeItemsImage, {
		click: Function.createDelegate(this, this._closeItemsImageClick)
	});
}

$ShoppingCart.prototype._clearHandlers = function()
{
	// remove event handlers to avoid memory leaks
	$clearHandlers(this._cartImage);
	$clearHandlers(this._closeItemsImage);

}

// -------------------- HTML Events ---------------------

$ShoppingCart.prototype._cartImageClick = function(e)
{
	this._itemsDiv.style.display = "block";
}

$ShoppingCart.prototype._closeItemsImageClick = function(e)
{
	this._itemsDiv.style.display = "none";
}

In the web page I have added some items to the shopping cart so we can test the functionality.

ShoppingCart.aspx.cs

	public partial class ShoppingCart : System.Web.UI.Page
	{
		protected void Page_Load(object sender, EventArgs e)
		{
			if(! IsPostBack)
			{
				var items = new List
				{
					new ShoppingCartItem{DateAdded = DateTime.Now.AddMinutes(-20),Text = "Learn Ajax in 1h!",
										Description = "If you want to learn Ajax in 1h, this is your book.",
										Quantity = 1,TotalPrice = 28.90f},
					new ShoppingCartItem{DateAdded = DateTime.Now.AddMinutes(-20),Text = "Ajax for Dummies!",
										Description = "If you think that you are hopeless, this is your book.",
										Quantity = 2,TotalPrice = 70.00f},
					new ShoppingCartItem{DateAdded = DateTime.Now.AddMinutes(-20),Text = "Advanced Ajax Server Controls",
										Description = "When you want to go further, this is your book.",
										Quantity = 1,TotalPrice = 86.50f},

				};

				Cart1.Items = items;
			}
		}
	}

Now you can run the example and click on the cart and close image so you can see how we have a server control that handles all the data in the server side and still can run some client functionality without doing any post back.

You can download the code here.

Important: I couldn’t upload the file as it was a zip file, so I added the extension jpg. After saving the file, remove the extension and unzip it normally.

How to create a complete AJAX Server Control. Part 1.

Posted on Updated on

This is the first post of a series of posts in where I’ll explain how to create an AJAX Server Control using MS AJAX Extensions and the AjaxControlToolkit (Not an Extender). I’ll be adding support to Postbacks, Callbacks, Syncronisation and other functionalities expected by our controls to make them useful in all kind of scenarios.

The control that I’m going to develop is a Shopping Cart like the one you can see in any of the online shopping web pages and I’ll be using very basic HTML and CSS style. My knowledge of CSS is quite basic so don’t expect to do very fancy styling (I’ll leave that to you).

In this first post I’m going to explain how to create the basis of the control. Just the basic required elements to have a working control, Server and Client.

Setting up the Solution and projects

Create a Solution with 2 projects, one ASP.NET AJAX Server Control in where we will add the control and one ASP.NET Web Application in where we will test it.

image

To simplify the work I’m going to make use of the AjaxControlToolkit library. There are a couple of Interfaces and Base classes that we can re use to avoid having to implement a lot of plumbering. Check as well that you have the Web references.

image

Creating the basic control

Our Ajax Server control is going to be formed by several parts:

  1. One C# class that will execute the server side of the control
  2. One JS file that will contain the client side representation of the control inside the browser
  3. One CSS file for the styles
  4. Some images used by the control

clip_image002[9] clip_image004[8]

What we have to remember is that the JS, CSS and image files are going to be embedded in the dll when we compile, so we have to go to the properties of each of the files and change the Build Action to Embedded Resource. Once we have done that we have to include them as WebReference so the files are ready to be used by the control and the Browser (We need to add [assembly: WebResource(“AjaxControl.Images.cart.png”, “img/png”)] directives for each of the files as shown in the following example).

ShoppingCart.cs

using AjaxControlToolkit;

// In the properties of the element set: Build Action -> Embedded Resource
[assembly: WebResource("AjaxControl.Images.cart.png", "img/png")]
[assembly: WebResource("AjaxControl.ShoppingCart.js", "application/x-javascript")]
[assembly: WebResource("AjaxControl.ShoppingCart.css", "text/css", PerformSubstitution = true)]

namespace AjaxControl
{
	[ClientCssResource("AjaxControl.ShoppingCart.css")]
	[ClientScriptResource("AjaxControl.ShoppingCart",
							"AjaxControl.ShoppingCart.js")]
	public class ShoppingCart : ScriptControlBase
	{
		const string ShoppingCartCSS = "ShoppingCart";

		public ShoppingCart(): base(false, HtmlTextWriterTag.Div)
		{
			this.Attributes["class"] = ShoppingCartCSS;
		}

		#region Control Rendering

		protected override void OnPreRender(EventArgs e)
		{
			if (!this.DesignMode)
			{
				if (ScriptManager.GetCurrent(Page) == null)
				{
					throw new HttpException("A ScriptManager control must exist on the current page.");
				}
				InitialiseControls();
			}
			base.OnPreRender(e);
		}

		private void InitialiseControls()
		{
			Image cartImage = new Image();
			cartImage.ID = "cartImage";
			cartImage.Attributes["class"] = "cartImage";
			cartImage.ToolTip = "Shopping Cart";
			cartImage.ImageUrl = Page.ClientScript.GetWebResourceUrl(this.GetType(),"AjaxControl.Images.cart.png");

			this.Controls.Add(cartImage);
		}
		#endregion
	}
}

In the sample we can see that the control inherits from AjaxControlToolkit.ScriptControlBase. This class inherits from public abstract class ScriptControl : WebControl, IScriptControl and gives us an implementation of the abstract methods IEnumerable<ScriptDescriptor> GetScriptDescriptors() and IEnumerable<ScriptReference> GetScriptReferences() that allows us to use the attributes [ClientCssResource] and [ClientScriptResource] to define our JS and CSS Resources in a declarative way.

To be able to use the images in the browser we can use Image controls and set the property ImageUrl = Page.ClientScript.GetWebResourceUrl(…) To generate an URL that will be used by the Browser to download the image file from the embedded objects in the DLL.

To do some work in the browser (AJAX functionality) we need to add the JS class that will handle all the functionality in the client side.

1. We first need to register the namespace of the control and create the constructor of the control.

2. Then we need to register the class at the end of the implementation, in where we specify that we are extending from Sys.UI.Control and that we will implement the Sys.IDisposable interface.

3. To finish we implement the initialize and dispose methods.

4. All the methods call the base methods to initialise the parent class.

Inheritance in JS it’s been added by MS AJAX Framework as a pattern and it’s a convention (it’s required to add certain lines of code to make it work in different locations and if you don’t do it, it won’t work as expected).

ShoppingCart.js


///

Type.registerNamespace('AjaxControl');

// Class Constructor:
// Will be called by the MS Ajax Framework to construct the client side reperentation of the control
var $ShoppingCart = AjaxControl.ShoppingCart = function(element)
{
	AjaxControl.ShoppingCart.initializeBase(this, [element]);
}

// Control Initialisation and Destruction--------------------------------------
$ShoppingCart.prototype.initialize = function()
{
	$ShoppingCart.callBaseMethod(this, "initialize");
}

// As the control implements Sys.IDisposable, the MS Ajax Framework requires of this method.
// This method will be use release used resources (specially required to work with Update Panels)
$ShoppingCart.prototype.dispose = function()
{
	$ShoppingCart.callBaseMethod(this, 'dispose');
}

//Register the ShoppingCart class with the client AJAX library and specify its base class as Sys.UI.Control.
$ShoppingCart.registerClass('AjaxControl.ShoppingCart', Sys.UI.Control, Sys.IDisposable);

ShoppingCart.css


.ShoppingCart
{
	padding: 2px;
	border: thin dotted #FF0000;
	background-color: #FFFF00;
}

Now it’s time to use the control in a web page. To do that, in the web project we add references to the project and the AjaxControlToolkit.dll. We can add the control directly to the page or use the Toolbox in VS to drag and drop the control to the page in Design mode.

ShoppingCart.aspx



    
    
    
<div>
        </div>



Have a look at the rendered HTML in the code. The links to the JS code, images… are generated as src=”/WebResource.axd?…” and =”/ScriptResource.axd?…”. Notice that not only your JS code is being referenced. MS AJAX JS libraries and the AjaxControlToolkit are downloaded as well.

Another piece of automatically generated piece of code is the creation of your control ($create). After the page is rendered an instance of your control in the client is created for you.

Rendered HTML:










//

<div>
<div id="Cart1" class="ShoppingCart" style="height:40px;width:40px;">
	   <img title="Shopping Cart" class="cartImage" src="/WebResource.axd?d=JwQFLCZRRtnD8Q0&amp;t=633807629642435710" style="border-width:0;" /></div>
</div>

//


Awesome, now we have the base of our Ajax Server Control that looks like the ones published in the AjaxControlToolkit.

You can download the code here.

Important: I couldn’t upload the file as it was a zip file, so I added the extension jpg. After saving the file, remove the extension and unzip it normally.