How to create custom widgets

Widgets questions and support

You can post your custom widgets and ask questions on our forum:
https://www.lansweeper.com/forum/yaf_topics26_Custom-Widgets.aspx

Creating a simple custom widget

The easiest widgets to make are those that simply show data, like the output of a SQL query, and don't require any input from the user. In this first example we will give a small example of how to make a small widget and how to add it to the website.

In order to make a widget, you first needs to create (or copy) an aspx-page in the appropriate website folder, being '/WidgetsCustom/'.

The first part of the aspx-file should always look something like this:

<%@ Page Language="C#" AutoEventWireup="true" Inherits="LS.BaseControl" %>

<%@ Import Namespace="System.Data" %>

<%@ Import Namespace="LS.Enums" %>

<%@ Import Namespace="LS" %>

<% Response.CacheControl = "no-cache";%>

<% Response.AddHeader("Pragma", "no-cache"); %>

<% Response.Expires = -1; %>

<%  LS.User.Current().CheckUserWebsiteAccess(); %>

 

Then follows the look up of data we want to show. For example if we want to show all the existing asset groups and the amount of assets assigned to each group, we can use this line of code:

var dsAssets = DB.ExecuteDataset("SELECT tblAssetGroups.AssetGroup, tblAssetGroups.AssetGroupid, COUNT(tblAssetGroupLink.AssetID) AS Count FROM tblAssetGroups INNER JOIN tblAssetGroupLink ON tblAssetGroups.AssetGroupID = tblAssetGroupLink.AssetGroupID GROUP BY tblAssetGroups.AssetGroup, tblAssetGroups.AssetGroupid");

 

All we need to do then is check if any data was gathered and if so show it in for example a table:

 

if (dsAssets.Rows.Count != 0)

{%>

<table width="100%" border="0" cellpadding="0" cellspacing="0">

 

    <% foreach (DataRow myrow in dsAssets.Rows)

    {%>

        <tr>

            <td align="left" valign="top">

                <%:myrow["AssetGroup"]%>

            </td>

            <td align="right" valign="top">

                <%:myrow["Count"]%>

            </td>

        </tr>

    <% }%>          

</table>

<%}%>

Next to showing data we can also show images or links to other pages, which make the widget more agreeable to look at and add some more functionality. You can use images which already exist in the '/images/' folder, or add images of your own to the '/WidgetsCustom/images' folder. To then use an image in the widget you simply need to provide the correct path in an image tag. Keep in mind that all paths need to contain the prefix '<%=ResolveUrl("~/")%>' . So if we wanted to add an image in front of each row in our table, we could add this piece of code to our table-row:

<td width="10" align="left" valign="top">

<img src="<%=ResolveUrl("~/")%>images/tag_green.png" width="16" height="16" hspace="2" vspace="2" />

</td>

 

A good link for this widget would be one that links to the page where asset groups can be added and assets can be added or removed from these groups. Keep in mind that since only certain people may make adjustments to asset groups, the web-role of the current user should be checked before showing this link.

<%if (LS.User.Current().IsInRole(Permission.EditConfiguration)){%><div style="float:right"><a href="<%=ResolveUrl("~/")%>configuration/AssetGroups/" class="sml">config</a></div><% } %>

 

A more advanced link would be one that links to a report with all assets in a certain group. To do this we require the DBobject 'web50getassetgroups' and the id of the asset group.

<td align="left" ><a href="<%=ResolveUrl("~/")%>report.aspx?det=web50getassetgroups&@assetgroupid=<%:myrow["AssetGroupid"]%>&title=Assets in group <%= HttpUtility.UrlEncode(myrow["AssetGroup"].ToString()) %>"><%: myrow["AssetGroup"] %></a></td>

 

There are still some more things we can do to improve our simple widget, like changing the title displayed on top of the widget and make the widget refresh every few seconds.  This last option is useful when data changes a lot, and is used in for example the scanning status widget.

<script type="text/javascript">

    $('#WTitle<%=TabControlID %>', window.top.document).text("Just a simple test widget");

</script>

 

In order for the autorefresh to work, we had to inherit LS.BaseControl in the heading.

<%=AutoRefresh(10) %>

 

Putting it all together  gives us the following:

<%@ Page Language="C#" AutoEventWireup="true" Inherits="LS.BaseControl" %>

<%@ Import Namespace="System.Data" %>

<%@ Import Namespace="LS.Enums" %>

<%@ Import Namespace="LS" %>

<% Response.CacheControl = "no-cache";%>

<% Response.AddHeader("Pragma", "no-cache"); %>

<% Response.Expires = -1; %>

<%  LS.User.Current().CheckUserWebsiteAccess(); %>

 

<%var dsAssets = DB.ExecuteDataset("SELECT tblAssetGroups.AssetGroup, tblAssetGroups.AssetGroupid, COUNT(tblAssetGroupLink.AssetID) AS Count FROM tblAssetGroups INNER JOIN tblAssetGroupLink ON tblAssetGroups.AssetGroupID = tblAssetGroupLink.AssetGroupID GROUP BY tblAssetGroups.AssetGroup, tblAssetGroups.AssetGroupid");

 

if (dsAssets.Rows.Count != 0)

{%>   

<table width="100%" border="0" cellpadding="0" cellspacing="0">

 

    <% foreach (DataRow myrow in dsAssets.Rows)

    {%>

        <tr>

            <td width="10" align="left" valign="top">

                <img src="<%=ResolveUrl("~/")%>images/tag_green.png" width="16" height="16" hspace="2" vspace="2" />

            </td>

            <td align="left" ><a href="<%=ResolveUrl("~/")%>report.aspx?det=web50getassetgroups&@assetgroupid=<%:myrow["AssetGroupid"]%>&title=Assets in group <%= HttpUtility.UrlEncode(myrow["AssetGroup"].ToString()) %>"><%: myrow["AssetGroup"] %></a></td>

            <td align="right" valign="top">

                <%:myrow["Count"]%>

            </td>

        </tr>

    <% }%>          

</table>

   

<%=AutoRefresh(10) %>

<%if (LS.User.Current().IsInRole(Permission.EditConfiguration)){%><div style="float:right"><a href="<%=ResolveUrl("~/")%>configuration/AssetGroups/" class="sml">config</a></div><% } %>

</div>

<%}%>

 

<script type="text/javascript">

    $('#WTitle<%=TabControlID %>', window.top.document).text("Just a simple test widget");

</script>

After creating the aspx-file, the website still needs to be informed you want to start using your new widget. In order for the website to find the widget, go to the widgets page on the Lansweeper site and add a new custom widget. The web page should correspond with the name of your aspx-file. If you want to give a custom image to your widget, you can put an icon in the folder '/WidgetsCustom/images/' and it should show up here. This icon preferably has dimensions 16x16.

Once this is done we should be able to find our new widget in the list of widgets on the Lansweeper main page and start using it.

Creating a more advanced custom widget

The second example will explain the code needed to let the user interact with the widget. We will first explain the code-behind and then what needs to be added to the aspx-page.

First of all we need to inherit LS.BaseControl, which will allow us to use the BaseControl class. This class helps us store data submitted by a user in an xml-structure.

public partial class WidgetsCustom_tester : LS.BaseControl

Before the Page_Load method we declare a XElement, which will hold all the data previously saved so that it can be displayed.

public XElement items;

Next we create a method which can be called at the appropriate time and will put all data in this XElement .

private static void ShowWidget(ref XElement items) {

    var bc = (LS.BaseControl)(HttpContext.Current.Handler);

    if(bc.State != null) items = bc.State.Element("items");

}

Then we focus on our Page_Load method. First we need to declare our BaseControl, and add the proper action to our widget's form (which should be given the id 'WidgetForm', see later).

var bc = (LS.BaseControl)(HttpContext.Current.Handler);

WidgetForm.Action = bc.FormAction;

Then we check if the user tried to add/alter some data by checking the 'SaveState'. If not, then the previously stored state is put in our BaseControl and our ShowWidget method is called.

bc.LoadState();

ShowWidget(ref items);

 

If some data was sent, then that data needs to be parsed into a XElement object. The first part of this object always needs to be called 'state', the rest can be chosen. Since we will be using just one simple input field (which we will be calling 'testinput'), we will only be needing one extra XElement, which in this case we called 'items'. Once the data is parsed and the XElement object added to our BaseControl we can save it and call our ShowWidget method.

bc.State = new XElement("state", "");

var filters = new XElement("items");

filters.Add(new XElement("item", HttpContext.Current.Request["testinput"]));

bc.State.Add(new XElement(filters));

 

bc.SaveState();

ShowWidget(ref items);

 

Our code-behind file will eventually look like this:

using System;

using System.Data;

using System.Web;

using System.Xml.Linq;

 

public partial class WidgetsCustom_tester : LS.BaseControl

{

    public XElement items;

    public DataTable charData = new DataTable();

 

    protected void Page_Load(object sender, EventArgs e)

    {

        string error;

        charData = LS.DB.ExecuteReport("SELECT AssetTypename,Total FROM web50repchartAssettype", out error);

 

        var bc = (LS.BaseControl)(HttpContext.Current.Handler);

        WidgetForm.Action = bc.FormAction;

        //set your edit form to the correct location

 

        if (bc.IsSaveState()) {

            bc.State = new XElement("state", "");

 

            var filters = new XElement("items");

            filters.Add(new XElement("item", HttpContext.Current.Request["testinput"]));

 

            bc.State.Add(new XElement(filters));

            //save the state to the database

            bc.SaveState();

            //refresh the webpage with the new saved state

            ShowWidget(ref items);

        }

        else {

            //load the state of the widget from the database

            bc.LoadState();

            //perform code to render your widget

            ShowWidget(ref items);

        }

    }

 

    private static void ShowWidget(ref XElement items) {

        var bc = (LS.BaseControl)(HttpContext.Current.Handler);

        if(bc.State != null) items = bc.State.Element("items");

    }

}

Now we still need to add our input fields to the aspx-page. The heading of our page isn't much different from the one in the simple widget example. Since we now inherit the BaseControl class in our code behind, we can change the inherit parameter to the class name of our code file.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="tester.aspx.cs" Inherits="WidgetsCustom_tester" %>

<%@ Import Namespace="System.Data" %>

<%@ Import Namespace="LS.Enums" %>

<%@ Import Namespace="LS" %>

<% Response.CacheControl = "no-cache";%>

<% Response.AddHeader("Pragma", "no-cache"); %>

<% Response.Expires = -1; %>

<%  LS.User.Current().CheckUserWebsiteAccess(); %>

Next we need to add the standard structure which we need to put in every widget page that handles user data.

<div class="dragbox-content">

   

    <form class="frmwidget" id="WidgetForm" runat="server" target="frmwidget">

        <div class="editpanel" style="display: none">

                        <asp:Button runat="server" CssClass="widgetssavebutton" Text="Save Settings"/>

 

        </div>

    </form>

</div>

Since we only handle one simple text input, we only have to add one input tag, which we will call 'testinput'. This needs to be place in the 'editpanel' div. Notice that we added the class 'required', which will give an error if the field was not filled in instead of trying to send the data.

<input name="testinput" id="testinput" type="text" class="required"/>

In order to show the data submitted, the following code can be used. We first check if there is data and if so display it.

<% if (items != null)

{ %>

   <p><%: items.Element("item").Value %></p>

<% } %>

Our aspx-file will look like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="tester.aspx.cs" Inherits="WidgetsCustom_tester" %>

<%@ Import Namespace="System.Data" %>

<%@ Import Namespace="LS.Enums" %>

<%@ Import Namespace="LS" %>

<% Response.CacheControl = "no-cache";%>

<% Response.AddHeader("Pragma", "no-cache"); %>

<% Response.Expires = -1; %>

<%  LS.User.Current().CheckUserWebsiteAccess(); %>

   

<div class="dragbox-content">

   

    <form class="frmwidget" id="WidgetForm" runat="server" target="frmwidget">

        <div class="editpanel" style="display: none">

            <input name="testinput" id="testinput" type="text" class="required"/>

            <asp:Button runat="server" CssClass="widgetssavebutton" Text="Save Settings"/>

        </div>

    </form>

 

<% if (items != null)

{ %>

   <p><%: items.Element("item").Value %></p>

<% } %>

 

 

</div>

 

<script type="text/javascript">

    $('#WTitle<%=TabControlID %>', window.top.document).text("Just a simple test widget");

</script>

 

At last to make the widget editable we have to check the checkbox on the widget page.

This was of course just a simple introduction as to what the possibilities are. For more advanced examples we would like to refer to the other widget files in the '/Widgets/' folder, like bookmarks.aspx.

Charts

You might have noticed that the widgets that show charts make use of reports to get their data. So all you have to do to add custom data to these widgets is make a custom report. This report requires two things: the query must return a column with names and a second column with values, and the name of the report must begin with 'Chart: '. Here we give an example which gets all asset groups and the amount of assets there are in each group.

In the report builder we configure the report and test the output:

When we then go back to the Lansweeper main page we'll be able to use this report with our chart widget.

 

Related Articles