Implementing an HTML Editor as a Server Control

Now that we've spent a time delving into an obscure portion of IE's Document Object Model (DOM), how does this apply to ASP.NET? Part of the power of ASP.NET is the ability to encapsulate a combination of client-side code and server-side code into a server control that is treated as a single logical unit. This unit can then be easily reused without rewriting the code every time we need that functionality. The rest of this chapter will show how to implement our HTML editor as a custom server control, and then how to use it. The basics of implementing custom server controls was introduced in Chapter 15. If you do not have any familiarity with custom server controls, you should read the portions of Chapter 15 that introduce custom server controls before you read the rest of this chapter.

Control Design

Before writing the custom control, we need to specify what we want the control to do and how we want to interact with it. Here is a list of characteristics that I thought the control should have in addition to supporting our basic task of implementing a client-side HTML editor:

  • The control should be able to be used with any method of data storage.

  • The control should be able to be sized using Visual Studio .NET's visual designer.

  • The look of the control should be controlled using a style sheet.

  • Multiple controls should be able to be placed on a single page without interfering with each other.

  • The control should be simple to use.

That is the basics of what we want the control to do, so now let's examine these points in a little more detail.

Data Storage

Designing the control so it can be used with any method of data storage is easy. You don't implement data storage in the control. Rather than having the control store data, the control can fire an event when a button is pressed indicating that the person editing data with the control is done and that the data should be saved. You could then either write the code to handle the data storage in the page hosting the control, or write other controls that inherit from this control and specialize the storage method. In this chapter, we will write the control with no data-storing capability and then show how to store data from the page hosting the control.

Using the Control with Visual Studio .NET

The simplest way to get our control to look decent in Visual Studio .NET is to use one of the existing Web controls as a base class. This gives us the functionality of that base class as a starting point, to which we can add additional functionality as necessary. I prefer the simplicity of Panel controls, so we will use that as our base class.

Using Style Sheets

The Panel control that we are using as our base class already exposes a property called CssClass. However, our control will have nested Panel controls to give the functionality we want, so we will expose properties to set the CssClass properties of the nested controls for additional flexibility. In our control, we will expose the MenuCssClass and EditorCssClass properties for this purpose.

Multiple Controls on a Page

The biggest concern with multiple controls on a given page is naming conflicts. The naming conflicts of any controls contained within our control can be overcome by having our control inherit from the INamingContainer interface. This is a marker interface that indicates to the compiler that the names of any contained controls should be concatenated with the name of our control to ensure uniqueness.

Simplicity

Simplicity is ensured by limiting the functionality of our control. Adding extra features may increase the usability, but features that are added to allow the control to be used only in certain unique circumstances should be avoided. Those types of features should be added by inheriting from the control that we have already created to form a specialized control, not by trying to add them into the base control. The class handles the common needs. Any niche concerns can be handled by extending the class through inheritance or aggregation.

Control Implementation

Now that we know the details of how an editor can be implemented on the client side in HTML, and we've defined the characteristics we want in the server control, it is time to get into the details of implementing the actual control.

Public Interface

The first thing you should do is to define the public interface. The public interface consists of the public methods, properties, and events that the end user of the control will see. Because we are trying to keep this control simple, our public interface will also be simple. Table 19.2 shows the details of the public interface.

Table 19.2. Public Interface

Name

Type

Description

Text

Property

This property contains the HTML being edited. This should be set with the HTML that is to be edited when the control is initialized. The updated HTML can be retrieved from this control after the Update event is fired.

MenuCssClass

Property

This property contains the name of the css class that will be used to format the menu portion of the control. This includes both the Panel for the toolbar and the Panel for the Update/Cancel buttons.

EditorCssClass

Property

This property contains the name of the css class that will be used to format the editor portion of the control.

Update

Event

This event is fired when the Update button is pushed. The Text property will contain the changed HTML data.

Cancel

Event

This event is fired when the Cancel button is pushed. The Text property will contain the unchanged HTML data.

Table 19.3. Private Helper Methods

Method

Description

CreateLiteral()

This method returns a LiteralControl created from the string passed into the method.

CreatePanel()

This method returns a Panel control. The values for the ID and CssClass properties are passed as arguments.

CreateButton()

This method returns a Button control. The values for the ID, Text, and CausesValidation properties are passed as arguments. This method is used to create the Update and Cancel buttons.

CreateImageButton()

This method returns an ImageButton control. The values for the ID, ImageUrl and ToolTip properties are passed as arguments. The value for the client-side OnClick event is also passed in as an argument. This method is used to create the image buttons on the HTML editor toolbar.

CreateList()

This method returns a DropDownList control. The value for the ID property is passed as an argument. Additionally, an array of names and an array of values are passed that are used to create the list items. Finally, the value for the client-side OnChange event is passed. This method is used to create the drop-down lists in the editor menu.

Table 19.4. Control Creation Methods

Method

Description

CreateChildControls()

The overridden method inherited from the base class that is called to create all of the child controls of our custom control.

CreateScripts()

This method creates the necessary JavaScript for the client-side scripting and adds it to the Controls collection as a LiteralControl.

CreateMenu()

This method creates a nested Panel control and adds all the toolbar buttons to this control.

CreateEditBody()

This method creates an editable Panel control that is used for the actual editing of HTML.

CreateMenu2()

This method creates a nested Panel control that contains the Update and Cancel buttons.

Table 19.5. Event Handlers

Method

Description

OnUpdate_Click

This method is called when the Update button is pressed. It stores the edited HTML and then fires the Update event.

OnCancel_Click

This method is called when the Cancel button is pressed. It fires the Cancel event without storing any changed HTML.

For this control, I wrote some private helper methods to reduce redundancy in the coding. These helper methods are shown in Table 19.3.

The actual work of creating the control is done in the overridden CreateChildControls() method. To reduce complexity, this method calls a number of other private methods that each creates a portion of the control. Table 19.4 shows the methods that are used to create the control.

The final methods in this control are the event handlers for the Update and Cancel buttons. These methods are shown in Table 19.5.

In addition to event handlers, our control exposes two events to be used by pages or controls that host our control. Table 19.6 shows the events that are exposed by our control.

The most difficult problem while writing this control was how to get the edited HTML back to the server. The HTML tag that is used as the HTML editor is a <div> tag. Because a <div> tag is a display type tag and not an input type tag, the data in the <div> tag is not automatically sent back to the server. I overcame this problem by placing a hidden input tag on the page, and copying the data from the innerHtml property of the <div> tag to the value property of the hidden input tag. This created a second problem because now I needed to have the Update button run some client-side script and do a post back to the server. The solution to this second problem was to use a CustomValidator control. If a validator control is not attached to a particular input control, it is called when the form is submitted. The CustomValidator was set to call the client-side script necessary to copy the edited HTML into the hidden input tag.

Table 19.6. Exposed Events

Event

Description

Update

This event is fired after the Update button is pressed. The handler for this event can obtain the modified HTML through the Text property. This event should be handled to store the modified HTML.

Cancel

This event is fired after the Cancel button is pressed. The HTML obtained through the Text property after this event is fired is the original unchanged HTML.

Listing 19.2 is the complete source of the custom control implementing our client-side HTML editor.

Listing 19.2 HTML Editor Server Control HTML_Editor.VB
 Imports System.ComponentModel Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Web.UI.HtmlControls <DefaultProperty("Text"), ToolboxData _     ("<{0}:HTML_EditorVB runat=server></{0}:HTML_EditorVB>")> _ Public Class HTML_EditorVB     Inherits System.Web.UI.WebControls.Panel     Implements INamingContainer     Event Update(ByVal sender As Object, ByVal e As EventArgs)     Event Cancel(ByVal sender As Object, ByVal e As EventArgs)     Private m_objMenu As Panel     Private m_objEditor As Panel     Private m_objMenu2 As Panel     Private m_objHidden As HtmlInputHidden     ' Properties     <Bindable(True), Category("Appearance"), DefaultValue("")> _     Property [Text]() As String         Get             If IsNothing(ViewState.Item("Text")) Then                 ViewState.Item("Text") = ""             End If             Return ViewState.Item("Text")         End Get         Set(ByVal Value As String)             ViewState.Item("Text") = Value         End Set     End Property     <Bindable(True), Category("Appearance"), DefaultValue("")> _     Property [MenuCssClass]() As String         Get             If IsNothing(ViewState.Item("MenuCssClass")) Then                 ViewState.Item("MenuCssClass") = ""             End If             Return ViewState.Item("MenuCssClass")         End Get         Set(ByVal Value As String)             ViewState.Item("MenuCssClass") = Value         End Set     End Property     <Bindable(True), Category("Appearance"), DefaultValue("")> _     Property [EditorCssClass]() As String         Get             If IsNothing(ViewState.Item("EditorCssClass")) Then                 ViewState.Item("EditorCssClass") = ""             End If             Return ViewState.Item("EditorCssClass")         End Get         Set(ByVal Value As String)             ViewState.Item("EditorCssClass") = Value         End Set     End Property     ' Methods     Protected Overrides Sub CreateChildControls()         CreateScripts()         CreateMenu()         CreateEditBody()         CreateMenu2()     End Sub     Private Sub CreateScripts()         Dim strScripts As String         strScripts = vbCrLf & "<script>" & vbCrLf & _             "function HTMLListCommand(editor,command,list)" & _             vbCrLf & "{" & vbCrLf & _             "editor.focus();" & vbCrLf & _             "editor.document.execCommand(command,false," & _             "list.opetions(list.selectedIndex).value);" & _             vbCrLf & "list.selectedIndex=0;" & vbCrLf & "}" & _             vbCrLf & "function HTMLBtnCommand(editor,command)" _             & vbCrLf & "{" & vbCrLf & "editor.focus();" & _             vbCrLf & _             "editor.document.execCommand(command,false,null);" _             & vbCrLf & "}" & vbCrLf & "</script>" & vbCrLf         Controls.Add(CreateLiteral(strScripts))     End Sub     Private Sub CreateMenu()         Dim arrayNames() As String         Dim arrayValues() As String         Dim strEditor As String         Dim strHandler As String         m_objMenu = CreatePanel("Menu", MenuCssClass)         Controls.Add(m_objMenu)         strEditor = UniqueID.Replace(":", "_") & "_Editor"         With m_objMenu.Controls             'Paragraph Settings             arrayNames = New String() _             { _                 "Paragraph","Normal","Heading 1","Heading 2", _                 "Heading 3","Heading 4","Heading 5", _                 "Heading 6","Directory List","Pre-Formatted", _                 "Address" _             }             arrayValues = New String() _             { _                 "", "Normal", "<h1>", "<h2>", "<h3>", "<h4>", _                 "<h5>", "<h6>", "<dir>", "<ore>", _                 "<address>" _             }             strHandler = "HTMLListCommand(" & strEditor & _                 ",'FormatBlock',this)"             .Add(CreateList("ParagraphList", arrayNames, _                 arrayValues, strHandler))             ' Font Names             arrayNames = New String() _             { _                 "Font","Arial","Arial Black","Comic Sans MS", _                 "Courier New", "Georgia", "Impact", _                 "Lucida Console", "Palatino Linotype", _                 "Trebuchet MS", "Verdana" _             }             strHandler = "HTMLListCommand(" & strEditor & _                 ",'FontName',this)"             .Add(CreateList("FontList", arrayNames, arrayNames, _                 strHandler))             ' Font Size             arrayNames = New String() {"Size", "1", "2", "3", _                 "4", "5", "6", "7"}             strHandler = "HTMLListCommand(" & strEditor & _                 ",'FontSize',this)"             .Add(CreateList("FontSizeList", arrayNames, _                 arrayNames, strHandler))             ' Fore Color             arrayNames = New String() {"Color", "Black", _                 "Blue", "Green", "Orange", "Red", "White", _                 "Yellow"}             strHandler = "HTMLListCommand(" & strEditor & _                 ",'ForeColor',this)"             .Add(CreateList("ColorList", arrayNames, _                 arrayNames, strHandler))             ' Bold             strHandler = "HTMLBtnCommand(" & strEditor & _                 ",'Bold')"             .Add(CreateImageButton("Bold", _                 "/HTML_Editor/images/bold.gif", "Bold", _                 strHandler))             ' Italic             strHandler = "HTMLBtnCommand(" & strEditor & _                 ",'Italic')"             .Add(CreateImageButton("Italic", _                 "/HTML_Editor/images/italic.gif", "Italic", _                 strHandler))             ' Justify Left             strHandler = "HTMLBtnCommand(" & strEditor & _                 ",'JustifyLeft')"             .Add(CreateImageButton("JustifyLeft", _                 "/HTML_Editor/images/justifyleft.gif", _                 "Left Justify Text", strHandler))             ' Justify Center             strHandler = "HTMLBtnCommand(" & strEditor & _                 ",'JustifyCenter')"             .Add(CreateImageButton("JusfifyCenter", _                 "/HTML_Editor/images/justifycenter.gif", _                 "Center Justify Text", strHandler))             ' Justify Right             strHandler = "HTMLBtnCommand(" & strEditor & _                 ",'JustifyRight')"             .Add(CreateImageButton("JusfifyRight", _                 "/HTML_Editor/images/justifyright.gif", _                 "Right Justify Text", strHandler))             ' Indent             strHandler = "HTMLBtnCommand(" & strEditor & _                 ",'Indent')"             .Add(CreateImageButton("Indent", _                 "/HTML_Editor/images/indent.gif", _                 "Indent Text", strHandler))             ' Outdent             strHandler = "HTMLBtnCommand(" & strEditor & _                 ",'Outdent')"             .Add(CreateImageButton("Outdent", _                 "/HTML_Editor/images/outdent.gif", _                 "Outdent Text", strHandler))             ' Cut             strHandler = "HTMLBtnCommand(" & strEditor & _                 ",'Cut')"             .Add(CreateImageButton("Cut", _                 "/HTML_Editor/images/cut.gif", "Cut", _                 strHandler))             ' Copy             strHandler = "HTMLBtnCommand(" & strEditor & _                 ",'Copy')"             .Add(CreateImageButton("Copy", _                 "/HTML_Editor/images/copy.gif", "Copy", _                 strHandler))             ' Paste             strHandler = "HTMLBtnCommand(" & strEditor & _                 ",'Paste')"             .Add(CreateImageButton("Paste", _                 "/HTML_Editor/images/paste.gif", "Paste", _                 strHandler))             ' Undo             strHandler = "HTMLBtnCommand(" & strEditor & _                 ",'Undo')"             .Add(CreateImageButton("Undo", _                 "/HTML_Editor/images/undo.gif", "Undo", _                 strHandler))             ' Redo             strHandler = "HTMLBtnCommand(" & strEditor & _                 ",'Redo')"             .Add(CreateImageButton("Redo", _                 "/HTML_Editor/images/redo.gif", "Redo", _                 strHandler))         End With     End Sub     Private Sub CreateEditBody()         m_objEditor = CreatePanel("Editor", EditorCssClass)         Controls.Add(m_objEditor)         With m_objEditor             .Controls.Add(CreateLiteral(Text))             .Attributes.Add("contentEditable", "True")         End With     End Sub     Private Sub CreateMenu2()         ' Create Panel for the Update/Cancel Buttons         m_objMenu2 = CreatePanel("Menu2", MenuCssClass)         Controls.Add(m_objMenu2)         'Create Update Button         Dim objUpdate As Button         objUpdate = CreateButton("Update", "Update", True)         AddHandler objUpdate.Click, AddressOf OnUpdate_Click         'Create Cancel Button         Dim objCancel As Button         objCancel = CreateButton("Cancel", "Cancel", False)         AddHandler objCancel.Click, AddressOf OnCancel_Click         'Create Hidden Field to return edited HTML         m_objHidden = New HtmlInputHidden()         m_objHidden.ID = "ctlHidden"         m_objMenu2.Controls.Add(m_objHidden)         'Client Validation Name         Dim strClientValidationName As String         strClientValidationName = UniqueID.Replace(":", "_") & _             "_UpdateHtml"         'Create Custom Validator         'Used to trigger the script to copy the         'edited HTML into the hidden control         Dim objValidator As New CustomValidator()         With objValidator             .ClientValidationFunction = strClientValidationName             .EnableClientScript = True         End With         m_objMenu2.Controls.Add(objValidator)         'Create Client Validation Script         Dim strHiddenName As String         Dim strEditorName As String         Dim strScript As String         strHiddenName = m_objHidden.UniqueID.Replace(":", "_")         strEditorName = m_objEditor.UniqueID.Replace(":", "_")         strScript = vbCrLf & "<script>" & vbCrLf & _             "{" & vbCrLf & _             "args.IsValid=true;" & vbCrLf & _             "document.all['" & strHiddenName & _             "'].value=document.all['" & strEditorName & _             "'].innerHTML;" & vbCrLf & _             "}" & vbCrLf & "</script>" & vbCrLf         m_objMenu2.Controls.Add(CreateLiteral(strScript))     End Sub     ' Helper Functions     Private Function CreateLiteral(ByVal strText As String) _     As LiteralControl         Return New LiteralControl(strText)     End Function     Private Function CreatePanel(ByVal strID As String, _     ByVal strCssClass As String) As Panel         Dim objPanel As New Panel()         With objPanel             .ID = strID             .CssClass = strCssClass         End With         Return objPanel     End Function     Private Function CreateButton(ByVal strID As String, ByVal _         strText As String, ByVal bCausesValidation As Boolean) _         As Button         Dim objButton As New Button()         With objButton             .ID = strID             .Text = strText             .CausesValidation = bCausesValidation         End With         Return objButton     End Function     Private Function CreateImageButton(ByVal strID As String, _         ByVal strImageUrl As String, ByVal strToolTip As String _         , ByVal strOnClick As String) As ImageButton         Dim objImageButton As New ImageButton()         With objImageButton             .ID = strID             .ImageUrl = strImageUrl             .ToolTip = strToolTip             .Attributes.Add("OnClick", "strOnClick")             .CausesValidation = False         End With         Return objImageButton     End Function     Private Function CreateList(ByVal strID As String, ByVal _         arrayNames() As String, ByVal arrayValues() As String, _         ByVal strOnChange As String) As DropDownList         Dim objList As New DropDownList()         Dim objItem As ListItem         Dim i As Int32         If arrayNames.Length <> arrayValues.Length Then             Throw New Exception( _             "arrayNames and arrayValues must be the same length")         End If         With objList             .ID = strID             .Attributes.Add("OnChange", strOnChange)             For i = 0 To arrayNames.Length - 1                 objItem = New ListItem()                 objItem.Text = arrayNames.GetValue(i)                 objItem.Value = arrayValues.GetValue(i)                 .Items.Add(objItem)             Next             .Items(0).Selected = True         End With         Return objList     End Function     ' Event Handlers     Private Sub OnUpdate_Click(ByVal sender As Object, ByVal e _         As EventArgs)         Text = m_objHidden.Value         With m_objEditor.Controls             .Clear()             .Add(CreateLiteral(Text))         End With         RaiseEvent Update(Me, e)     End Sub     Private Sub OnCancel_Click(ByVal sender As Object, ByVal e _         As EventArgs)         RaiseEvent Cancel(Me, e)     End Sub End Class 

To actually use the control, you need to place the images for the image buttons in the /HTML_Editor/images folder of the Web server. These images can be downloaded from www.ASPNET-Solutions.com.



ASP. NET Solutions - 24 Case Studies. Best Practices for Developers
ASP. NET Solutions - 24 Case Studies. Best Practices for Developers
ISBN: 321159659
EAN: N/A
Year: 2003
Pages: 175

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net