In this topic
About developing a scriptable Web ADF control
A key consideration when designing a custom Web control is where the control's logic executes. While all logic can be put on the client or Web tier, the best approach is to place some logic on each tier.
User interface (UI) data and operations should be situated on the client tier, while data retrieval and heavy computational logic should (and often must) reside on the Web tier. Distributing the control this way maximizes the use of client resources and reduces server load. To minimize network traffic, client tier functionality should initiate server tier logic only when necessary (for example, to query a database located on the Web tier). Adhering to this architecture enables the development of controls with rich and responsive UIs.
Given the advantages of this architecture, the question becomes one of implementation. How can you efficiently create an implementation that is intuitive, maintainable, extensible, and re-distributable? One effective approach is to create a scriptable ASP.NET server control that inherits from the Web Application Developer Framework (ADF) WebControl. Scriptable server controls have a framework to combine server and client logic into one easily redistributable control. This framework allows the partitioning of client and server logic as previously described and keeps that logic located in one control, adherent to object oriented programming (OOP) standards.
Inheriting from the Web ADF WebControl automatically packages the Microsoft ASP.NET Asynchronous JavaScript XML (AJAX) and Web ADF JavaScript libraries with the control, making these available to client tier operations. This is the approach used by Web ADF developers to implement Web controls.
Implementing a MapCoordinateDisplay control
The following steps show how to implement a MapCoordinateDisplay control, which displays the current position of the mouse cursor over a buddied Map control:
- Create a ASP.NET Server Control project in Visual Studio 2008.
See the following screen shot that shows selecting the ASP.NET Server Control template on the New Project dialog box in Visual Studio: - Right-click the References option on the Solution Explorer to add references to the ESRI.ArcGIS.ADF.Web.UI.WebControls.dll, System.Web.Extensions.dll, and AjaxControlToolkit assemblies.
See the following screen shot that shows the References option on the Solution Explorer: - Rename the default name of the server control (ServerControl1) or Web control (WebControlLibrary1) to MapCoordinateDisplay and derive the class from the base ADF Web control class. Change the name in the ToolboxData attribute and the class declaration. See the following code example:
namespace MapCoordinateDisplay
{
[DefaultProperty("Text")][ToolboxData(
"<{0}:MapCoordinateDisplay runat=server></{0}:MapCoordinateDisplay>")]
public class MapCoordinateDisplay: ESRI.ArcGIS.ADF.Web.UI.WebControls.WebControl
{
[VB.NET]
<DefaultProperty("Text"), _
ToolboxData("<{0}:MapCoordinateDisplay runat=server></{0}:MapCoordinateDisplay>")
- Delete the default stub implementation of RenderContents inserted by Visual Studio.
- Replace the getter and setter for the Text property to use StateManager to store state. See the following code example:
public string Text
{
get
{
return StateManager.GetProperty("text")as string;
}
set
{
StateManager.SetProperty("text", value);
}
}
[VB.NET]
Public Property Text() As String
Get
Return TryCast(StateManager.GetProperty("text"), String)
End Get
Set(ByVal Value As String)
StateManager.SetProperty("text", Value)
End Set
End Property
- To buddy to the Map control, add a property to store the map's ID. Store the ID and not the reference to the control so that the property can be specified in markup and the control is not stored in session state, which causes memory leaks. For more information, see Developing custom Web ADF controls. See the following code example:
/// <summary>The ID of the Map control to associate with this control.</summary>
public string Map
{
get
{
return StateManager.GetProperty("map")as string;
}
set
{
StateManager.SetProperty("map", value);
}
}
[VB.NET]
Public Property Map() As String
Get
Return TryCast(StateManager.GetProperty("map"), String)
End Get
Set(ByVal Value As String)
StateManager.SetProperty("map", Value)
End Set
End Property
- Add a Label control as a member variable. This is used to show the map coordinates. See the following code example:
public class MapCoordinateDisplay: ESRI.ArcGIS.ADF.Web.UI.WebControls.WebControl
{
private Label m_displayLabel;
[VB.NET]
Public Class MapCoordinateDisplay
Inherits ESRI.ArcGIS.ADF.Web.UI.WebControls.WebControl
Private m_displayLabel As Label
- Override the CreateChildControls method to control the creation of sub controls. See the following code example:
protected override void CreateChildControls()
{
Controls.Clear();
base.CreateChildControls();
m_displayLabel = new Label();
m_displayLabel.ID = "DisplayLabel";
m_displayLabel.Text = Text;
m_displayLabel.Font.Bold = true;
Controls.Add(m_displayLabel);
}
[VB.NET]
Protected Overrides Sub CreateChildControls()
Controls.Clear()
MyBase.CreateChildControls()
m_displayLabel = New Label()
m_displayLabel.ID = "DisplayLabel"
m_displayLabel.Text = Text
m_displayLabel.Font.Bold = True
Controls.Add(m_displayLabel)
End Sub
- For the client script component, add a new folder and name it, javascript. Add a new JScript file and name it, MapCoordinateDisplay.js. Set the file's build action to Embedded Resource.
See the following screen shots that show the MapCoordinateDisplay.js file in the javascript folder and the Embedded Resource build action: - Right-click the AssemblyInfo.cs file/Assemblyinfo.vb, select Properties, then set the build action to Embedded Resource. Pay attention to the namespace and casing of the WebResource name attribute. See the following code example:
[assembly: System.Web.UI.WebResource(
"MapCoordinateDisplay.javascript.MapCoordinateDisplay.js", "text/javascript")]
[VB.NET]
<Assembly
System.Web.UI.WebResource("MapCoordinateDisplay.js", "text/javascript")>
- To include the JavaScript auto-magically (that is, automatically) with the server control, add the custom attribute to the class definition. Pay attention to the namespace and casing of the ClientScriptResource attribute. See the following code example:
namespace MapCoordinateDisplay
{
[DefaultProperty("Text")][ToolboxData(
"<{0}:MapCoordinateDisplay runat=server></{0}:MapCoordinateDisplay>")
][AjaxControlToolkit.ClientScriptResource(
"MapCoordinateDisplay.MapCoordinateDisplay",
"MapCoordinateDisplay.javascript.MapCoordinateDisplay.js")]
public class MapCoordinateDisplay: ESRI.ArcGIS.ADF.Web.UI.WebControls.WebControl
{
[VB.NET]
Namespace MapCoordinateDisplay
<DefaultProperty("Text"), _
ToolboxData("<{0}:MapCoordinateDisplay runat=server></{0}:MapCoordinateDisplay>"), _
AjaxControlToolkit.ClientScriptResource("MapCoordinateDisplay.MapCoordinateDisplay", "MapCoordinateDisplay.js")> _
- Add the following code example in the MapCoordinateDisplay.js file to create a client side representation of the control. Use the ASP.NET AJAX framework for convenient namespace and class registration. Give the control AJAX client control functionality by specifying inheritance from the ASP.NET AJAX client control base class (Sys.UI.Control) in the call to registerClass. Since your server control inherits from the Web ADF WebControl base class, you can assume that ASP.NET AJAX functions are available to be called.
/// <reference name="MicrosoftAjax.js"/>
/// <reference assembly="ESRI.ArcGIS.ADF.Web.UI.WebControls" name="ESRI.ArcGIS.ADF.Web.UI.WebControls.Runtime.JavaScript.references.js"/>
Type.registerNamespace('MapCoordinateDisplay');
MapCoordinateDisplay.MapCoordinateDisplay = function(element){
MapCoordinateDisplay.MapCoordinateDisplay.initializeBase(this, [element]);
}
MapCoordinateDisplay.MapCoordinateDisplay.prototype = {
initialize: function(){
MapCoordinateDisplay.MapCoordinateDisplay.callBaseMethod(this, 'initialize');
// Add custom initialization here.
}
, dispose: function(){
//Add custom dispose actions here.
MapCoordinateDisplay.MapCoordinateDisplay.callBaseMethod(this, 'dispose');
}
}
MapCoordinateDisplay.MapCoordinateDisplay.registerClass
('MapCoordinateDisplay.MapCoordinateDisplay', Sys.UI.Control);
if (typeof(Sys) != = 'undefined')
Sys.Application.notifyScriptLoaded();
- Add a property to the component to encapsulate access to the buddied Map control. See the following code example:
MapCoordinateDisplay.MapCoordinateDisplay = function(element){
MapCoordinateDisplay.MapCoordinateDisplay.initializeBase(this, [element]);
this._map = null;
}
MapCoordinateDisplay.MapCoordinateDisplay.prototype = {
initialize: function(){
MapCoordinateDisplay.MapCoordinateDisplay.callBaseMethod(this, 'initialize');
// Add custom initialization here.
}
, get_map: function(){
return this._map;
}
, set_map: function(value){
this._map = value;
}
, dispose: function(){
//Add custom dispose actions here.
MapCoordinateDisplay.MapCoordinateDisplay.callBaseMethod(this, 'dispose');
}
}
- Add another property to store the reference to the Document Object Model (DOM) element rendered by the label (child) control you included in the server control implementation. This element can then be manipulated using Dynamic Hypertext Markup Language (DHTML) and JavaScript. See the following code example:
MapCoordinateDisplay.MapCoordinateDisplay = function(element){
MapCoordinateDisplay.MapCoordinateDisplay.initializeBase(this, [element]);
this._map = null;
this._displayLabel = null;
}
MapCoordinateDisplay.MapCoordinateDisplay.prototype = {
initialize: function(){
MapCoordinateDisplay.MapCoordinateDisplay.callBaseMethod(this, 'initialize');
// Add custom initialization here.
}
, get_map: function(){
return this._map;
}
, set_map: function(value){
this._map = value;
}
, get_displayLabel: function(){
return this._displayLabel;
}
, set_displayLabel: function(value){
this._displayLabel = value;
}
, dispose: function(){
//Add custom dispose actions here.
MapCoordinateDisplay.MapCoordinateDisplay.callBaseMethod(this, 'dispose');
}
}
- Attach an event listener to the map's mouseMove event. Wrap the listener in a call to createDelegate so that the keyword (this) in the listener refers to the MapCoordinateDisplay control. See the following code example:
MapCoordinateDisplay.MapCoordinateDisplay.prototype = {
initialize: function(){
MapCoordinateDisplay.MapCoordinateDisplay.callBaseMethod(this, 'initialize');
// Add custom initialization here.
if (this._map){
this._map.add_mouseMove(Function.createDelegate(this,
this._onMapMouseMove));
}
}
, _onMapMouseMove: function(sender, args){
// Display the coordinates.
}
, get_map: function(){
return this._map;
}
,
- Define the handler for the mouseMove event to show the coordinates by manipulating the innerHTML property of the label using DHTML and JavaScript. See the following code example:
_onMapMouseMove: function(sender, args){
// Display the coordinates.
if (this._displayLabel){
this._displayLabel.innerHTML = args.coordinate.toString();
}
}
,
- The remaining part of your scriptable control implementation is including code that creates a corresponding MapCoordinateDisplay client control for any Web tier instance. To do this, construct JavaScript to instantiate the client control with the relevant server control properties, then register this script so that it executes on application initialization. Since your control derives from the base ADF Web Control, you can safely reference ASP.NET AJAX events and functions. Specify this initialization code in the OnPreRender method. See the following code example:
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
if (!base.IsAsync)
{
StringBuilder script = new StringBuilder();
script.Append("Sys.Application.add_init(function() {");
script.Append("$create(MapCoordinateDisplay.MapCoordinateDisplay,");
script.AppendFormat("{{\"displayLabel\":$get('{0}')",
m_displayLabel.ClientID);
script.AppendFormat("}}, null, {{\"map\":\"{0}\"}}, $get('{1}'));", Map,
ClientID);
script.AppendLine("});");
ScriptManager.RegisterStartupScript(this, typeof(MapCoordinateDisplay),
this.ClientID + "_startup", script.ToString(), true);
}
}
[VB.NET]
Protected Overrides Sub OnPreRender(ByVal e As EventArgs)
MyBase.OnPreRender(e)
If (Not MyBase.IsAsync) Then
Dim script As StringBuilder = New StringBuilder()
script.Append("Sys.Application.add_init(function() {")
script.Append("$create(MapCoordinateDisplay.MapCoordinateDisplay,")
script.AppendFormat("{{""displayLabel"":$get('{0}')", m_displayLabel.ClientID)
script.AppendFormat("}}, null, {{""map"":""{0}""}}, $get('{1}'));", Map, ClientID)
script.AppendLine("});")
ScriptManager.RegisterStartupScript(Me, GetType(MapCoordinateDisplay), Me.ClientID & "_startup", script.ToString(), True)
End If
End Sub
- To test the control, build the control assembly and deploy it in an application with a test page that includes a map and map resource manager. Buddy the new control to the map. At runtime, when the cursor hovers over the map, the map coordinates in the new script control change accordingly.
See the following screen shot that shows the control deployed in an application:
See Also:
Map controlWorking with the Map control
Developing custom Web ADF controls