3. Adding a Menu Item and Dialog Control to Map.aspx
Define the MenuItem

In the constructor of MeasureExtenderComponent create a MenuItem and return it from the ControlMenuItem property getter method.
public class MeasureExtenderComponent : Cadcorp.Web.UI.Interfaces.IWMLExtenderComponent { private MenuItem MeasureMenuItem; public MeasureExtenderComponent() { MeasureMenuItem = new MenuItem() { ID = "CustomMeasureMenuId", Text = "Custom Measure", DialogID = "CustomMeasureMenuId", ScriptContentPlaceHolder = "scriptcontainer" }; } public MenuItem ControlMenuItem { get { return MeasureMenuItem; } }

MenuItem is a control class provided by Cadcorp that adds a functioning Menu button to the map.
The ID property is used to link a DialogBase class to it, along the DialogID property. The Text property sets the label text seen on the control in ‘map.aspx’. The DialogID must be set to the ID of the DialogBase we want to open when the Menu is clicked by the user. This will be defined in the next step of this topic.
ScriptContentPlaceHolder is a necessary value and is the identity of a control defined in ‘map.aspx’ which is used to load JavaScript files into the generated site.
Define the DialogBase

Create a new .cs file ‘CustomMeasureDialog.cs’ and add the following code:
using Cadcorp.Web.UI.Common.Util;
using System;
using System.Web.UI;
using System.Web.UI.HtmlControls
// WebResourceAttribute exposes the embedded resource.
[assembly: WebResourceAttribute("CustomMeasureDialogExtender.Resources.CustomMeasureDialog.js", "text/javascript")]
namespace CustomMeasureDialogExtender
{
public class CustomMeasureDialog :
Cadcorp.Web.UI.Controls.Dialog.DialogBase
{
private string _titleLabel;
private string _bodyTextContainerId;
/// <summary>
/// Constructor. Initialise label strings, etc. here.
/// </summary>
/// <param name="dialogClass">Dialog class should be the name of the JavaScript Dialog class.</param>
public CustomMeasureDialog(string dialogClass) : base(dialogClass)
{
_titleLabel = "Distance measured:";
_bodyTextContainerId = "bodyContainer";
}
/// <summary>
/// Override GetConfig. Builds a JSON string to be passed to the JavaScript object of class "dialogClass" when it is instantiated.
/// </summary>
/// <returns>String JSON representation of configuration values.</returns>
public override string GetConfig()
{
return string.Format("{0}, textContainerId: {1}, ",
base.GetConfig(), // base.GetConfig must be included as part of the string.
// Cadcorp.Web.UI.Common.Util includes several helper methods for converting .Net values into valid JSON DataTypes:
Utilities.GetJsonString("MainContent_" + this.ID +'_'+ this._bodyTextContainerId) // This is the composite ID created automatically based on the nesting of the child controls.
);
}
/// <summary>
/// Override CreateChildControls, to create render custom controls onto the page.
/// This adds a title with text decided on the server, and a container Div element we will fill in client side.
/// The ID of the container element is sent to the client in the GetConfig method to be used as a CSS selector by the client-side JavaScript.
/// </summary>
protected override void CreateChildControls()
{
// Create HTML controls on the server.
HtmlGenericControl myContainer = new HtmlGenericControl("div") { ID = "textContainer" };
HtmlGenericControl title = new HtmlGenericControl("h1") { ID = "title" };
title.InnerHtml = this._titleLabel;
myContainer.Controls.Add(title);
HtmlGenericControl bodyContainer = new HtmlGenericControl("div") { ID = this._bodyTextContainerId };
// We will be adding the body text client-side with javascript, so just add the container div element for now.
myContainer.Controls.Add(bodyContainer);
Controls.Add(myContainer);
base.CreateChildControls();
}
/// <summary>
/// Override OnPreRender, to emit BasicDialog.js. Without this the JavaScript would not be loaded by the browser.
/// </summary>
/// <param name="e"></param>
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
// EmitJavascriptFrom accepts the name of the JavaScript file.
// The location parameter is the Default Namespace of the project, followed by the folder, and then the JavaScript file.
EmitJavascriptFrom("CustomMeasureDialog.js", "CustomMeasureDialogExtender.Resources.CustomMeasureDialog.js", GetType());
}
}
}

Create a folder called “Resources” and add a new JavaScript file called “CustomMeasureDialog.js” and in Visual Studio set its Build Action to Embedded Resource.

Add the following code to the new JavaScript file.
Cadcorp.namespace('Cadcorp.CustomMeasureExtender');
// Inherit from the provided class 'Cadcorp.UI.DialogBase'.
Cadcorp.CustomMeasureExtender.Dialog = Cadcorp.Util.Class(Cadcorp.UI.DialogBase, {
// These are the expected properties from the server.
textContainerId: null,
labels: null,
measureControl: null,
/*
* Override the constructor. This is executed once when the page loads.
* 'config' parameter: accepts the JSON returned by the accompanying DialogBase GetConfig method.
* construct on the DialogBase prototype adds each of the JSON properties to this object automatically.
*/
construct: function (config) {
Cadcorp.UI.DialogBase.prototype.construct.call(this, config);
// The 'mapafterinit' event is used to make sure the map has initialised before attempting to add OpenLayers controls or layers.
// Anything that requires the map object before mapafterinit is triggered may fail arbitrarily.
Cadcorp.Framework.events.register('mapafterinit', this, this.afterInitHandler);
},
afterInitHandler: function (eventArgs){
// The OpenLayers.Map object is passed to the handler on the eventArgs parameter.
var map = eventArgs.map;
//Create, configure and then add the OpenLayers Control to the Map.
this.measureControl = new
OpenLayers.Control.Measure(OpenLayers.Handler.Path, { persist: true, autoActivate: false });
this.measureControl.events.on ( {
"measure": Cadcorp.Util.createDelegate(this, this.measureHandler),
"measurepartial": Cadcorp.Util.createDelegate(this, this.measureHandler),
});
map.addControl(this.measureControl);
},
show: function () {
// Make sure we execute the regular show behaviour, then activate our measure control.
Cadcorp.UI.DialogBase.prototype.show.call(this, arguments);
this.measureControl.activate();
},
closeHandler: function (event, ui) {
// Make sure the regular closeHandler is executed, then deactivate our measure control.
Cadcorp.UI.DialogBase.prototype.closeHandler.call(this, arguments);
this.measureControl.deactivate();
},
measureHandler: function (eventArgs) {
// First check the container div exists, then set the innerhtml to the measured distance.
var textContainerDiv = document.getElementById(this.textContainerId);
if (textContainerDiv !== undefined && textContainerDiv !== null) {
textContainerDiv.innerHTML = eventArgs.measure.toFixed(3) + ' ' + eventArgs.units;
}
}
});

Update MeasureExtenderComponent, creating a CustomMeasureDialog in the constructor and ensuring the ControlDialog property returns it.
private DialogBase MeasureDialog;
private MenuItem MeasureMenuItem;
public MeasureExtenderComponent()
{
MeasureDialog = new
CustomMeasureDialog("Cadcorp.CustomMeasureExtender.Dialog") { ID =
"CustomMeasureDialogId", MenuId = "ExampleMenuId", ScriptContentPlaceHolder
= "scriptcontainer", DialogControllerID = this.DialogControllerID, FooterId
= this.FooterId, TitleText = "Custom Measure Dialog", CssClass = "hidden"
};
Need MenuID and DialogID to match.
MeasureMenuItem = new MenuItem() { ID = "CustomMeasureMenuId", Text =
"Custom Measure", DialogID = "MeasureDialogId", ScriptContentPlaceHolder =
"scriptcontainer" };
}
public DialogBase ControlDialog
{
get
{
return MeasureDialog;
}
}
public MenuItem ControlMenuItem
{
get
{
return MeasureMenuItem;
}
}

The string passed to the CustomMeasureDialog constructor must match the class name defined in ‘CustomMeasureDialog.js’.
The Cadcorp.Framework.events object exposes several useful events the developer can attach handlers to. In this example, the ‘mapafterinit’ event, is called after the OpenLayers.Map object is fully initialised allowing custom layers and controls to be added safely.