Overview
The Wisej.NET premium extensions bring six commercial JavaScript component suites into Wisej.NET applications:
| Suite | Extension | Base class | Widget classes | NuGet package |
|---|---|---|---|---|
| DevExpress DevExtreme | Wisej.Web.Ext.DevExtreme | dxBase | 38 (dxDataGrid, dxChart, dxScheduler, …) | Wisej-4-DevExtreme |
| Syncfusion Essential JS 2 | Wisej.Web.Ext.Syncfusion2 | ej2Base | 50 (Grid, Chart, Schedule, Chat, …) | Wisej-4-Syncfusion2 |
| Syncfusion Essential JS 1 | Wisej.Web.Ext.Syncfusion | ejBase | 54 (ejGrid, ejChart, ejSchedule, …) | Wisej-4-Syncfusion |
| Telerik Kendo UI | Wisej.Web.Ext.Kendo | kendoBase | 49 (kendoGrid, kendoChart, kendoScheduler, …) | Wisej-4-Kendo |
| Infragistics Ignite UI | Wisej.Web.Ext.Ignite | igBase | 28 (igGrid, igDataChart, igSpreadsheet, …) | Wisej-4-Ignite |
| Webix | Wisej.Web.Ext.Webix | wxBase | 36 (DataTable, Pivot, Kanban, …) | Wisej-4-Webix |
Every package targets .NET Framework 4.8 and .NET 8 or later (including the -windows, Android, iOS and Mac Catalyst targets).
How the wrappers work
The premium extensions don't rebuild each vendor's component model as a .NET class hierarchy, which would always lag behind the vendor and cover only part of it. Instead, each widget class is a thin wrapper that gives you the whole JavaScript API of the component: every option, method and event the vendor documents.
- Each suite has one base class,
dxBase,ej2Base,ejBase,kendoBase,igBaseorwxBase, derived from the Wisej.NETWidget. The base class loads the vendor's scripts and styles, registers the licence key, and routes options, method calls and events between the server and the browser. - Each component is a small class that names the JavaScript class it wraps. For example,
dxDataGridcreates adxDataGrid, and the EJ2Gridcreates anej.grids.Grid. Some classes add a.jsfile that makes event data serializable, fits the component to its container, or adds typed properties such asTextorValue. - The vendor's scripts and styles are loaded from a CDN, from your server, or from embedded resources, as set in a configuration file.
Because the wrappers are thin, the vendor's documentation is the reference for what each widget can do. This page and the vendor pages cover the Wisej.NET side: how to reach that API from C# and VB.NET.
The premium extensions build on the same mechanism as the generic Widget control. Its user guide page and Extend Widgets explain the underlying ideas in more detail.
Licensing
- A licence from the component vendor for the JavaScript suite you use: DevExpress, Syncfusion, Progress Telerik, Infragistics or XB Software (Webix). It's a commercial licence that you buy directly from the vendor, separate from Wisej.NET. Wisej.NET doesn't include or resell it.
- A Wisej.NET licence that includes the premium extensions: a Professional or Enterprise developer licence, or the Technology Partner program. The premium extensions aren't available in the Community edition.
The binaries are published on NuGet. The source code is in a private GitHub repository that only Technology Partners with source code access can open.
Most vendors also expect a licence key at runtime. Without one, their components may show a trial banner or watermark. Where the wrapper supports a key, you set it in the extension's configuration file.
Adding a widget
- Add the NuGet package of the suite to your project, for example
dotnet add package Wisej-4-Syncfusion2. - Rebuild. The widget classes appear in the Visual Studio toolbox.
- Drop a widget on a page and set its Options in the property grid, or create it in code.
- Optionally, add a configuration file to choose where the vendor's scripts come from, to set the licence key and to set the default theme.
Options
Every widget has three kinds of members: options, methods and events. The options are the configuration object you'd pass to the component in JavaScript, and they're exposed in .NET through a single dynamic property, Options. The vendors usually list them under Configuration or Properties in their API reference.
You can set Options in three ways:
- In the designer, by pasting a JSON string into the Options property. It's parsed into the dynamic object.
- Field by field, on the dynamic object.
- In one go, by assigning an object: an anonymous type, a class of your own, or a JSON string.
- C#
- VB.NET
// Field by field. First-level fields update the widget automatically...
this.dxCircularGauge1.Options.containerBackgroundColor = "blue";
// ...but changes further down need an explicit Update().
this.dxCircularGauge1.Options.scale.startValue = 0;
this.dxCircularGauge1.Options.scale.endValue = 50;
this.dxCircularGauge1.Update();
// An anonymous type reads almost like the JavaScript configuration.
this.dxCircularGauge1.Options.rangeContainer.ranges = new object[] {
new { startValue = 50, endValue = 90 },
new { startValue = 90, endValue = 130 },
new { startValue = 130, endValue = 150 }
};
this.dxCircularGauge1.Update();
' Field by field. First-level fields update the widget automatically...
Me.DxCircularGauge1.Options.containerBackgroundColor = "blue"
' ...but changes further down need an explicit Update().
Me.DxCircularGauge1.Options.scale.startValue = 0
Me.DxCircularGauge1.Options.scale.endValue = 50
Me.DxCircularGauge1.Update()
' An anonymous type reads almost like the JavaScript configuration.
Me.DxCircularGauge1.Options.rangeContainer.ranges = New Object() {
New With {.startValue = 50, .endValue = 90},
New With {.startValue = 90, .endValue = 130},
New With {.startValue = 130, .endValue = 150}
}
Me.DxCircularGauge1.Update()
The widget detects changes to first-level fields only. After changing a nested field such as scale.startValue, call Update(), or tell the options object what changed with Options.Notify("scale.startValue"). Assigning a whole first-level object, such as Options.scale = new { ... }, needs neither.
Option names are the vendor's JavaScript names and are case-sensitive: dataSource, not DataSource. A misspelled option isn't an error, just an option the component ignores.
When you assign the whole Options property, the base class converts the value, anonymous types included, into a Wisej.NET dynamic object, so you can go on changing single fields afterwards. An anonymous object assigned to a nested field stays read-only: to change one of its values, assign a new object.
Methods
Every method of the JavaScript component can be called from the server through the dynamic Instance property. Each call is sent to the browser. To get a return value, pass a callback as the last argument, or call the Async version that every method gets automatically and await it.
- C#
- VB.NET
// Fire and forget: calls value(15) on the gauge in the browser.
this.dxLinearGauge1.Instance.value(15);
// With a return value: await the Async twin of the method.
var selection = await this.dxDataGrid1.Instance.getSelectedRowKeysAsync();
// Or pass a callback as the last argument.
this.dxDataGrid1.Instance.getSelectedRowKeys(
new Action<dynamic>(keys => this.ShowSelection(keys)));
' Requires Option Strict Off: Instance is a dynamic object.
' Fire and forget: calls value(15) on the gauge in the browser.
Me.DxLinearGauge1.Instance.value(15)
' With a return value: await the Async twin of the method.
Dim selection = Await Me.DxDataGrid1.Instance.getSelectedRowKeysAsync()
Instance and Options are dynamic objects, which VB.NET resolves at runtime only with late binding. Put Option Strict Off at the top of the files that use them.
Custom JavaScript functions
Sometimes a method returns something the server can't use, such as a DOM node or an object with circular references, or a task needs several calls in a row inside the browser. In those cases, add a JavaScript function to the widget with the WidgetFunctions property, in the designer or in code, and call it by name with Call.
Inside the function, this is the Wisej.NET widget and this.widget is the vendor's component. arguments holds the values passed to Call.
- C#
- VB.NET
this.dxHtmlEditor1.WidgetFunctions = new[] {
new dxBase.WidgetFunction {
Name = "formatRange",
Source = @"
this.widget.formatText(arguments[0], arguments[1], {
bold: arguments[2],
underline: arguments[3]
});"
}
};
// Bold and underline the first five characters.
this.dxHtmlEditor1.Call("formatRange", 0, 5, true, true);
// The same result with a direct method call.
this.dxHtmlEditor1.Instance.formatText(0, 5, new { bold = true, underline = true });
Me.DxHtmlEditor1.WidgetFunctions = {
New dxBase.WidgetFunction With {
.Name = "formatRange",
.Source = "
this.widget.formatText(arguments[0], arguments[1], {
bold: arguments[2],
underline: arguments[3]
});"
}
}
' Bold and underline the first five characters.
Me.DxHtmlEditor1.Call("formatRange", 0, 5, True, True)
WidgetFunction, WidgetEventHandler and WidgetTemplate are nested in each suite's base class: dxBase.WidgetFunction, ej2Base.WidgetFunction, and so on.
Give each function a name that the wrapper doesn't already use. A function that clashes with an existing member isn't registered, and the error only shows up in the browser's JavaScript console.
A widget function can also supply the value of an option. Set the option to a string that names the function, and the wrapper swaps in the function when the component is created:
| Option value | Result |
|---|---|
"()=>myFunction" | The option is the function. Use this for callbacks such as a custom formatter. |
"(value, index)=>myFunction" | The same, with named parameters. |
"()=>myFunction()" | The function is called once, and the option gets its return value, for example a data source object built in JavaScript. |
"()=>myFunction(1,'a')" | The same, called with arguments. |
Events
JavaScript components report what happens through callbacks in their options. The wrappers turn those callbacks into .NET events in three ways.
1. Attach a handler through Instance (C# only). Prefix the vendor's event name with on. The wrapper wires the event in the browser the first time you attach to it.
// https://js.devexpress.com/Documentation/ApiReference/UI_Components/dxGantt/Events/#taskClick
this.dxGantt1.Instance.onTaskClick += new WidgetEventHandler(this.dxGantt1_TaskClick);
private void dxGantt1_TaskClick(object sender, WidgetEventArgs e)
{
// e.Data is a dynamic object holding the event data sent by the widget.
AlertBox.Show("Clicked task " + e.Data.key);
}
2. Call AddListener (C# and VB.NET). This is what VB.NET must use, since it can't attach to dynamic events with AddHandler or Handles.
Me.DxBullet1.AddListener("optionChanged", AddressOf Me.DxBullet1_OptionChanged)
Private Sub DxBullet1_OptionChanged(sender As Object, e As WidgetEventArgs)
' e.Type is the event name, e.Data the event data.
End Sub
3. Handle the widget's WidgetEvent, which receives every event the widget sends to the server. Check e.Type to tell them apart.
The event data in WidgetEventArgs.Data is copied from the vendor's event object, keeping primitive values, dates, arrays and nested objects, and skipping DOM elements, browser events and references to the widget itself. The copy goes a few levels deep only, so read deep structures through a method call or a widget function instead.
Each event you attach to becomes a round trip to the server every time it fires. Attach to high-frequency events, such as pointer moves, scrolling or optionChanged, only when you really need them. Otherwise handle them in the browser with a JavaScript handler (below).
JavaScript event handlers
To handle an event in the browser, without a round trip, add it to the WidgetEvents collection. The code runs in the browser whenever the component fires the event. e is the vendor's event object, this is the vendor's component, and e.container is the Wisej.NET widget.
- C#
- VB.NET
this.dxDataGrid1.WidgetEvents = new[] {
new dxBase.WidgetEventHandler {
Name = "onRowPrepared",
Source = @"
if (e.rowType === 'data' && e.data.overdue)
e.rowElement.style.color = 'red';"
}
};
Me.DxDataGrid1.WidgetEvents = {
New dxBase.WidgetEventHandler With {
.Name = "onRowPrepared",
.Source = "
if (e.rowType === 'data' && e.data.overdue)
e.rowElement.style.color = 'red';"
}
}
Name is the name of the option that takes the callback, exactly as the vendor documents it. For DevExtreme that's the on… form, such as onRowPrepared. For Syncfusion and Kendo it's the plain event name, such as rowDataBound or change. For Webix, give the event name without its on prefix, such as itemClick: the wrapper adds on itself.
Remember which this you're in. In a WidgetEvents handler, this is the vendor's component. In a WidgetFunctions function, this is the Wisej.NET widget and the vendor's component is this.widget.
Templates
Syncfusion, Kendo UI, Ignite UI and Webix components often take HTML templates, for example for a grid cell or a list item. The ej2Base, ejBase, kendoBase, igBase and wxBase classes have a WidgetTemplates collection that registers each template in the page as a <script id="…" type="…"> block. The options can then refer to the template by its id. DevExtreme uses JavaScript functions for templates instead, so use a widget function there.
| Property | What it does |
|---|---|
Id | The id of the <script> element, which the options refer to, for example "#statusTemplate". |
Template | The template markup, in the vendor's template syntax. |
Type | The type of the <script> element. The default depends on the suite: text/x-kendo-template for Kendo UI, text/x-jquery-tmpl for Ignite UI, and text/x-jsrender for the others. |
- C#
- VB.NET
// A Syncfusion EJ2 grid column rendered from a template.
this.grid1.WidgetTemplates = new[] {
new ej2Base.WidgetTemplate {
Id = "statusTemplate",
Type = "text/x-template",
Template = "<span class='status-${Status}'>${Status}</span>"
}
};
this.grid1.Options.columns = new object[] {
new { field = "OrderID", headerText = "Order" },
new { field = "Status", headerText = "Status", template = "#statusTemplate" }
};
' A Syncfusion EJ2 grid column rendered from a template.
Me.Grid1.WidgetTemplates = {
New ej2Base.WidgetTemplate With {
.Id = "statusTemplate",
.Type = "text/x-template",
.Template = "<span class='status-${Status}'>${Status}</span>"
}
}
Me.Grid1.Options.columns = New Object() {
New With {.field = "OrderID", .headerText = "Order"},
New With {.field = "Status", .headerText = "Status", .template = "#statusTemplate"}
}
A template is added to the page once, the first time a widget that declares it is rendered, and it stays there. Give templates ids that are unique across the application.
Other premium extensions
The premium repository also has extensions that don't have a page in this manual yet: HighCharts, MobiScroll (Wisej-4-MobiScroll), DevExpress Dashboard (Wisej-4-DxDashboard), Dynamsoft (Wisej-4-Dynamsoft) and Telerik Report Viewer. Technology Partners with source code access will find them in the premium repository.