Tuesday 4 August 2009

Creating a LiveZilla Button Server Control

Due to a customers request for a specific feature, I’ve just discovered LiveZilla. It’s a really good and FREE live chat program for your website.

It basically requires putting a small piece of JavaScript into your page wherever you want the Live Chat button to appear. This code can contain “variables” to help identify the person browsing your site and what part of the site they are on.

The problem I had was updating this code. The LiveZilla Server comes with a nifty program to generate this code and initially I started by placing the code at runtime into an ASP.NET Literal Control. This soon became a pain whenever I wanted to update the code and had to manually double-quote all of the Javascript so I could place it in a string variable, replace the variables in the code (allowing for HTML encoding) with the values to identify the user browsing the site and then writing it out to the Literal control.

Another spanner was thrown in the works because the site(s) I was using this on are sold to multiple customers (hosted separately) and each “version” of the site needed to use code specific to the customer. Well not wanting to have to put customer specific code into my pages, I needed a way to populate the Chat buttons easily and dynamically from data retrieved from a database.

What I wanted to achieve was:

  • Chat buttons populated from database.
  • Ability to use different Chat buttons on different pages
  • Each button should populate with customer specific code.

Here’s what I did:-

Create a new ASP.Net Server Control project called “LiveZillaButton”. This creates a project and gives you the following code to get you started (I’m using VS2008):

Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Text
Imports System.Web
Imports System.Web.UI
Imports System.Web.UI.WebControls


<DefaultProperty("Text"), ToolboxData("<{0}:ServerControl1 runat=server></{0}:ServerControl1>")> _
Public Class ServerControl1
    Inherits WebControl

    <Bindable(True), Category("Appearance"), DefaultValue(""), Localizable(True)> Property Text() As String
        Get
            Dim s As String = CStr(ViewState("Text"))
            If s Is Nothing Then
                Return "[" + Me.ID + "]"
            Else
                Return s
            End If
        End Get

        Set(ByVal Value As String)
            ViewState("Text") = Value
        End Set
    End Property

    Protected Overrides Sub RenderContents(ByVal output As HtmlTextWriter)
        output.Write(Text)
    End Sub

End Class

I replaced the default “Text” property with my own “Tag” property. This property is going to be used to identify the database record that the button should get it’s code from. I’ve altered the attributes so the property appears in it’s own “Livezilla” section in the property grid instead of the “Appearance” section.

<Bindable(True), Category("Livezilla"), DefaultValue("")> _
Property Tag() As String
    Get
        Dim s As String = CStr(ViewState("Tag"))
        Return If(String.IsNullOrEmpty(s), String.Empty, s)
    End Get

    Set(ByVal Value As String)
        ViewState("Tag") = Value
    End Set
End Property
I now add a TrackingCode Property. This will hold the Javascript code if I decide to populate the button manually instead of using the Tag property.
<Bindable(True), Category("Livezilla"), DefaultValue("")> _
Property TrackingCode() As String
    Get
        Dim s As String = CStr(ViewState("TrackingCode"))
        Return If(String.IsNullOrEmpty(s), String.Empty, s)
    End Get

    Set(ByVal Value As String)
        ViewState("TrackingCode") = Value
    End Set
End Property
I need to raise an event before the control outputs it’s HTML that will allow me to customise it to my requirements. To do this I need some way of passing data to and from the event handler. So I created a new Class in the project with the following code:-
Public Class LivezillaEventargs
    Inherits EventArgs

    Public Sub New(ByVal tag As String, ByVal ButtonContent As String)
        _tag = tag
        _buttonContent = ButtonContent
    End Sub

    Private _tag As String
    Public ReadOnly Property Tag() As String
        Get
            Return _tag
        End Get
    End Property

    Private _buttonContent As String
    Public Property ButtonContent() As String
        Get
            Return _buttonContent
        End Get

        Set(ByVal Value As String)
            _buttonContent = Value
        End Set
    End Property

End Class

Back to the main control, I need an Event and also some default HTML that will be used when the none of the controls properties are populated. Lets add the following to our control.

Friend Const EmptyButton = "<div style=""border: thin groove #000080; margin: 0px; padding: 0px; width: 150px; height: 80px""><table style=""width: 100%; height: 100%""><tr><td style=""height: 30px; font-weight: bold; font-family: Arial, Helvetica, sans-serif; text-align: center; color: #000080;"">Livezilla</td></tr><tr><td style=""font-size: 11px; vertical-align: middle; text-align: center; font-family: Arial, Helvetica, sans-serif;"">Populate the TrackingCode property or handle the BeforeRender event</td></tr></table></div>"

Public Event BeforeRender(ByVal sender As Object, ByRef e As LivezillaEventargs)

Remember we still have a “RenderContents” Method that has been complaining about it’s lack of a “Text” property since we replaced it with a Tag property. Modify this method so it now contains:-

Protected Overrides Sub RenderContents(ByVal output As HtmlTextWriter)
    Dim TC As String = TrackingCode
    Dim EV As New LivezillaEventargs(Tag, TC)

    RaiseEvent BeforeRender(Me, EV)

    If String.IsNullOrEmpty(EV.ButtonContent) Then
        EV.ButtonContent = EmptyButton
    End If

    output.Write(EV.ButtonContent)
End Sub

This raises our event and allows us to manipulate the HTML before it is output.

The button now is fully functional and you can build the project and add it to the toolbox. However, you may want to make a few more changes first for better design time support.

The control attributes still have their default values so let’s change them to :-

<DefaultEvent("BeforeRender"), DefaultProperty("TrackingCode"), ToolboxData("<{0}:LivezillaButton runat=server></{0}:LivezillaButton>")> _
Double clicking the Livezilla button on your web form will now automatically create the BeforeRender Event Handler and dropping the Button onto your page will highlight the TrackingCode property by default. I also renamed the control from it’s default of “ServerControl1”.
Add a new file to your project called “AssemblyInfo.vb” and type the following into it:-
Imports System.Web.UI

<Assembly: TagPrefix("LiveZillaButton", "LZ")> 
This gives our control a custom Tag prefix otherwise our button would be created on the web form with the default prefix of cc1. The next part is totally optional. We don’t necessarily want the control to render the button at design time so we need to tell it to do something different when we are working on the design surface. Add a reference to System.Design to the project and then create a new class containing:-
Friend Class LivezillaControlDesigner
    Inherits System.Web.UI.Design.ControlDesigner

    Protected Button As LivezillaButton

    Public Overrides Sub Initialize(ByVal component As System.ComponentModel.IComponent)
        If TypeOf component Is LivezillaButton Then
            MyBase.Initialize(component)
            Button = DirectCast(component, LivezillaButton)
        End If
    End Sub

    Public Overrides Function GetDesignTimeHtml() As String
        Return If(String.IsNullOrEmpty(Button.TrackingCode), LivezillaButton.EmptyButton, Button.TrackingCode)
    End Function

End Class

This is the designer that will handle our design time HTML. As it stands, it just outputs our standard button HTML but you can modify the GetDesignTimeHtml method to return whatever you want to use instead. I’ll leave this part up to you.

The final step is to tell our button to use our new ControlDesigner so add the following attribute to the LiveZillaButton Class.

Designer(GetType(LivezillaControlDesigner))
And that’s it. The control is finished. To use it, just drop the button onto your web form and either:-
  1. Populate the TrackingCode property if you want a static button with specific markup.
  2. Populate the Tag property and handle the BeforeRender Event to dynamically change the markup.
e.g

Protected Sub LivezillaButton1_BeforeRender(ByVal sender As Object, ByRef e As LiveZillaButton.LivezillaEventargs) Handles LivezillaButton1.BeforeRender
    If e.Tag = "LoginPage" Then
        e.ButtonContent = "Our Button Content - Possibly from a database"
    Else
        e.ButtonContent = "Our other Button content"
    End If
End Sub