Creating a JS empowered custom control

Creating the custom control

First we need to create a new “web form user control” in our web application:

ascx

 

Embedding external libraries

For using any external JS controls probably some additionally css files and js libraries has to be embedded in your application. If so the default.aspx file should be modified in the following way:

<head runat="server">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Main Page</title>
    <meta http-equiv="Expires" content="0" />

    <link rel="stylesheet" href="./css/somecss.min.css">
    <script type="text/javascript" src="./js/somejs.min.js"></script>
</head>

Because we want to keep our demonstration project as simple as possible we do not concern about connected topics like using CDNs or minimizing and bundling those external code sequences. Additionally we want to create a DevExpress dxChart control in our sample project (for which all css and js libraries are allready included in any XAF web per default) so for the moment we dont have to worry about enhancing our default.aspx file.

 

Embedding the JS control

A valid approach for embedding any JS control in your web form user control is to use a devexpress control as parent and include the js control into this container element. The following lines show such an implementation:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ChartControl.ascx.cs" Inherits="CustomJSControl.Web.Controls.ChartControl" %>
<%@ Register Assembly="DevExpress.Web.v16.1, Version=16.1.7.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" Namespace="DevExpress.Web" TagPrefix="dx" %>

<dx:ASPxPanel ID="ASPxPanel1" runat="server" Width="100%" Height="500px">
   <ClientSideEvents Init="function(s, e) { DxCode.CustomControl.createControl(s); }" />
</dx:ASPxPanel>

As you may foresee the actual initialization of the js control does take place in some additional “createControl” method after the container element was created.

The main advantages using a DevExpress control as a container element for our JS control are:

  • We can use the client side event “Init” to create our JS controls on demand
  • We can use the DevExpress js custom properties any DevExpress ASP.Net control is offering to communicate from the server to the client side
  • We can use the “GetMainElement” method of the DevExpress control to get a reference to the container itself in which we will load the JS control itself

 

Initialize the control

The JS control creation sequence takes place in some additional external js file:

window.DxCode = window.DxCode|| {};
window.DxCode.ChartControl = {
 createControl: function (panel) {
 var $mainElement = $(panel.GetMainElement());
 $mainElement.dxChart(
 // ...
 );
 }
};

To include this js code into the custom control the “onload” event of the web form control has to get overridden:

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

     string url = this.ResolveClientUrl("~/js/chartControl.js");
     WebWindow.CurrentRequestWindow.RegisterClientScriptInclude("chartControl", url);
 }

We could embed this js code sequence directly in the ASCX file but doing so would hinder the multiple usage of such a control in one view.

 

Embedding the control in the UI

To import the ASCX control into any view of our XAF web application you can add a new CustomUserControlWebViewItem object into the items collection of any view and link this item to the corresponding ASCX control:

model

After doing so you can alter the layout of the corresponding view to set the size and positioning of the control:

layouter

 

Sending messages to the control

After setting up the core ingredients we need to tell the JS control what kind of data it has to visualize. In most cases some kind of key information has to be sent to the client-side (e.g. the GUID of the current object) which can be used to load and process further detail data in advance. For this purpose the custom JS properties of the DevExpress container control comes in handy: With the help of an interface…

    public interface IBoundCustomControl
    {
        void SetOid(Guid oid);
    }

the controls can expose a public method…

  public partial class ChartControl : System.Web.UI.UserControl, IBoundCustomControl
  {
     public void SetOid(Guid oid)
     {
        if (this.ASPxPanel1.JSProperties.ContainsKey("cpOid"))
        {
           this.ASPxPanel1.JSProperties["cpOid"] = oid;
        }
        else
        {
           this.ASPxPanel1.JSProperties.Add("cpOid", oid);
        }
    }
    // ...

which sets a client side property (“cpOid”) on demand. This method has to get called on server-side after the current object is known and the underlying controls are initialized. For this purpose you can use a corresponding XAF view controller. In theOnViewControlsCreated event of this controller the “ControlCreated” event of the corresponding UI element get hooked…

 public partial class ChartControlViewController : ViewController
 {
    public ChartControlViewController()
    {
       InitializeComponent();
       // Target required Views (via the TargetXXX properties) and create their Actions.
       TargetObjectType = typeof(ParentObject);
       TargetViewType = ViewType.DetailView;
 }

 protected override void OnViewControlsCreated()
 {
    base.OnViewControlsCreated();

    // Access and customize the target View control.
    var view = (DetailView)View;

    ViewItem customControl = view.FindItem("ChartControl");
    if (customControl != null)
    {
       customControl.ControlCreated += CustomControl_ControlCreated;
    }
 }
 // ...

and the interface method gets called…

 private void CustomControl_ControlCreated(object sender, EventArgs e)
 {
    ViewItem customControl = sender as ViewItem;

    var currentObject = View.CurrentObject as ParentObject;
    if (currentObject != null)
    {
       var control = (IBoundCustomControl)customControl.Control;
       control.SetOid(currentObject.Oid);
    }
 }

On client-side this custom property can easily be read via the following code:

 ASPxPanel1.cpOid

 

Loading data from the database

To connect all pieces so far the control should be empowered to load asynchronically any data out of the application BOM, in most cases filtered to some existing reference data (e.g. some entity GUID) which was exposed via the custom js property (although it is possible to expose all data via some custom js property this approach is not asynchronically and therefor not advisable). So we want to communicate with our Web.API service. If we need some filter mechanism we only have to extend the controller method with the needed filter parameters…

  public IHttpActionResult GetChartData(Guid parentOid)
  {
     using (var uow = new UnitOfWork())
     {
        uow.ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;

        var resultList = new XPQuery<ChildObject>(uow).Where(w => w.ParentObject.Oid == parentOid);
        // ...

so that we can call this service on client-side via the following code:

var uri = 'http://localhost/api/dashboard/GetHistoryData?customer=' + panel.cpChartDataFilter;
$.getJSON(uri)

Now we can use the default async mechanism of javascript to load and process the resultset in any way…

 window.DxCode = window.DxCode || {};
 window.DxCode.ChartControl = {
    createControl: function (panel) {
       var $mainElement = $(panel.GetMainElement());

       var uri = 'http://localhost:2064/api/chartdata/GetChartData?parentOid=' + panel.cpOid;
       $.getJSON(uri)
          .done(function (dataSource) {
             $mainElement.dxChart(
                {
                   dataSource: dataSource,
                   commonSeriesSettings: {
                      argumentField: 'Argument',
                      valueField: 'Value'
                   },
                   series: [{
                      type: 'bar'
                   }]
                }
             );
       });
    }
 };

and with this we finally fullfilled the requirement to embed any js control in any view of our XAF web application…

result

 

Next…

Summary