Putting It Together The HtmlEditor Example


Putting It Together ”The HtmlEditor Example

In this section, we'll put together the pieces of implementing client-side features that we described earlier in the chapter and create an HTML editor server control. The HtmlEditor control enables the user to enter formatted text as an HTML fragment and edit the HTML source in a WYSIWYG fashion. HtmlEditor could be useful in applications that involve online content editing, Web-based page authoring, guestbook entries, and so on.

The control uses client script and DHTML behaviors that are available in Internet Explorer 5.5 and later to implement its editing user interface (UI) as a custom HTML tag or element ( <h:HtmlArea> ). HtmlEditor also uses the HTML editing feature built into Internet Explorer that is available by setting the contentEditable attribute of an element to true . When these client features are not available, the HtmlEditor control generates a standard HTML <textarea> that can be used to enter the HTML source manually.

Figure 13-1 illustrates the HTML editing UI generated by the HtmlEditor control.

Figure 13-1. The HTML editing UI generated by the HtmlEditor control allows the entry of formatted text.

graphics/f13hn01.jpg

Listing 13-1 lists the sample page used to test the HtmlEditor control. In particular, this code demonstrates how the page does not have to provide any logic to incorporate client-side behavior.

Listing 13-1 HtmlEditorTest.aspx
 <%@Pagelanguage="c#"%> <%@RegisterTagPrefix="msp"Assembly="MSPress.ServerControls" Namespace="MSPress.ServerControls"%><html> <scriptrunat="server"> protectedvoidButton1_Click(objectsender,EventArgse){ if(HtmlEditor1.Text.Equals(String.Empty)==false){ Label1.Visible=true; Label1.Text=HtmlEditor1.Text; HtmlEditor1.Visible=false; Button1.Visible=false; } } </script> <head> <title>HtmlEditorSample</title> </head> <body> <formid="HtmlEditorTest"method="post"runat="server"> <p> <msp:HtmlEditorid="HtmlEditor1"runat="server" Width="75%"Height="200px"Text="EdittheHTML..."/> <asp:Labelid="Label1"runat="server"Visible="false"/> <br/> <br/> <asp:Buttonid="Button1"runat="server"Text="Submit" OnClick="Button1_Click"/> </p> </form> </body> </html> 

The emphasized HTML in Listing 13-2 shows the HTML rendered by the HtmlEditor control.

Listing 13-2 The HTML rendered by the sample page
 <html>  <body> <formname="HtmlEditorTest"method="post" action="HtmlEditorTest.aspx"id="HtmlEditorTest"> 
  <scriptlanguage="JavaScript" src="/aspnet_client/mspress_servercontrols/1_0_0_0/HtmlArea.js"> </script> <?xml:namespaceprefix="h"/> <?importnamespace="h" implementation= "/aspnet_client/mspress_servercontrols/1_0_0_0/HtmlArea.htc"/>  <textareaid="HtmlEditor1"name="HtmlEditor1"style="display:none;"> EdittheHTML...</textarea> <h:HtmlAreaid="HtmlEditor1HtmlArea" onHtmlChanged= "ha_OnHtmlChanged(this,document.all['HtmlEditor1'])" style="height:200px;width:75%;"> </h:HtmlArea>  <scriptlanguage="JavaScript"> <!-- varHtmlAreaList=newArray('HtmlEditor1'); //--> </script> <scriptlanguage="JavaScript">ha_InitializeElements()</script>  </form> </body> </html> 

Listing 13-3 contains the code for the HtmlEditor control.

Listing 13-3 HtmlEditor.cs
 usingSystem; usingSystem.ComponentModel; usingSystem.ComponentModel.Design; usingSystem.Collections; usingSystem.Collections.Specialized; usingSystem.Reflection; usingSystem.Web; usingSystem.Web.UI; usingSystem.Web.UI.WebControls; namespaceMSPress.ServerControls{ //HtmlEditorprovidesarichHTMLeditoronInternetExplorer5.5 //andlaterthatallowsWYSIWYGHTMLeditingandentry. //Onotherbrowsers,thecontrolrendersasaTextArea,requiring //userstoenterHTMLsourcemanually. publicclassHtmlEditor:TextBox{ privatestaticstringClientFilesUrlPrefix; 
 privatebool_renderClientScript; publicHtmlEditor(){ base.TextMode=TextBoxMode.MultiLine; } [ Category("Behavior"), DefaultValue(true), Description("Whethertoenabletheclientscript-basedHTMLeditor.") ] publicboolEnableClientScript{ get{ objectb=ViewState["EnableClientScript"]; return(b==null)?true:(bool)b; } set{ ViewState["EnableClientScript"]=value; } } [ Browsable(false), DefaultValue(TextBoxMode.MultiLine), EditorBrowsable(EditorBrowsableState.Never) ] publicoverrideTextBoxModeTextMode{ get{ returnbase.TextMode; } set{ if(value!=TextBoxMode.MultiLine){ thrownewArgumentOutOfRangeException("value"); } base.TextMode=value; } } privatevoidDetermineRenderClientScript(){ //Inaclientscript-enabledcontrol,alwaysdetermine //whethertorendertheclientscript-basedfunctionality. //Thedecisionshouldbebasedonbothbrowser //capabilitiesandonthepagedeveloper'schoice. 
 _renderClientScript=false; if((Page!=null)&&(Page.Request!=null)){ //Thepagedevelopercandecidetoturnoffscript //completely. if(EnableClientScript){ //Thenextsetofchecksinvolveslookingatthe //capabilitiesofthebrowsermakingtherequest. HttpBrowserCapabilitiesbrowserCaps= Page.Request.Browser; boolhasEcmaScript= (browserCaps.EcmaScriptVersion.CompareTo(newVersion(1,2))>=0); boolhasDOM=(browserCaps.MSDomVersion.Major>=4); boolhasBehaviors= (browserCaps.MajorVersion>5) ((browserCaps.MajorVersion==5)&& (browserCaps.MinorVersion>=.5)); _renderClientScript= hasEcmaScript&&hasDOM&&hasBehaviors; } } } privatestringGetClientFileUrl(stringfileName){ if(ClientFilesUrlPrefix==null){ //Usetheconfigsettingtodeterminewheretheclient //filesarelocated.Clientfilesarelocatedinthe //aspnet_clientv-rootandthendistributedinto //subfoldersbyassemblynameandassemblyversion. stringlocation=null; if(Context!=null){ IDictionaryconfigData= (IDictionary)Context.GetConfig("system.web/webControls"); if(configData!=null){ location= (string)configData["clientScriptsLocation"]; } } if(location==null){ location=String.Empty; } 
 elseif(location.IndexOf("{0}")>=0){ AssemblyNameassemblyName= this.GetType().Assembly.GetName(); stringassembly= assemblyName.Name.Replace('.','_').ToLower(); stringversion= assemblyName.Version.ToString().Replace('.','_'); location= String.Format(location,assembly,version); } ClientFilesUrlPrefix=location; } returnClientFilesUrlPrefix+fileName; } privatestringGetClientIncludes(){ returnString.Format("<scriptlanguage=\"JavaScript\"src=\"{0}\">"+ "</script>\r\n"+ "<?xml:namespaceprefix=\"h\"/>\r\n"+ "<?importnamespace=\"h\"implementation=\"{1}\"/>", GetClientFileUrl("HtmlArea.js"), GetClientFileUrl("HtmlArea.htc")); } protectedoverridevoidOnPreRender(EventArgse){ base.OnPreRender(e); DetermineRenderClientScript(); if(_renderClientScript){ stringscriptKey=typeof(HtmlEditor).FullName; Page.RegisterClientScriptBlock(scriptKey, GetClientIncludes()); Page.RegisterArrayDeclaration("HtmlAreaList", "'"+ClientID+"'"); Page.RegisterStartupScript(scriptKey, "<scriptlanguage=\"JavaScript\">"+ "ha_InitializeElements()"+"</script>"); } } 
 protectedoverridevoidRender(HtmlTextWriterwriter){ if(_renderClientScript==false){ //UsethedefaultTextBoxrendering. base.Render(writer); } else{ writer.RenderBeginTag(HtmlTextWriterTag.Span); writer.AddAttribute(HtmlTextWriterAttribute.Id, ClientID); writer.AddAttribute(HtmlTextWriterAttribute.Name, UniqueID); writer.AddStyleAttribute("display","none"); writer.RenderBeginTag(HtmlTextWriterTag.Textarea); writer.Write(Text); writer.RenderEndTag(); writer.WriteLine(); if(ControlStyleCreated){ ControlStyle.AddAttributesToRender(writer,this); } writer.AddAttribute(HtmlTextWriterAttribute.Id, ClientID+"HtmlArea"); writer.AddAttribute("onHtmlChanged", "ha_OnHtmlChanged(this,document.all['"+ ClientID+"'])", false); writer.RenderBeginTag("h:HtmlArea"); writer.RenderEndTag(); writer.RenderEndTag();//Span } } } } 

HtmlEditor is similar to a multiline TextBox that allows the entry of HTML-formatted text. Therefore, HtmlEditor derives from the standard ASP.NET TextBox control. By doing so, HtmlEditor inherits all the postback-handling and event-raising logic of TextBox . The control limits its inherited TextMode property to the TextBoxMode.MultiLine value, and simply adds the client-side behavior needed to create a WYSIWYG editing UI. When the requesting browser does not provide the right level of client-side functionality, HtmlEditor simply degrades to an ordinary TextBox (or <textarea> ).

The HtmlEditor implementation shown in Listing 13-3 illustrates the following aspects of incorporating client-side functionality, which are generally applicable to all controls with client-side behavior:

  • Provides an EnableClientScript property whose default value is true . This property allows the page developer to completely turn off the client-side functionality for a specific control instance by disabling the logic it contains to automatically detect the capabilities of the Web browser client.

  • Includes a method named DetermineRenderClientScript to encapsulate the logic the control uses to discover the capabilities of the requesting browser and determine whether it should activate its client-side behavior. This method starts by assuming that it should not activate its client-side behavior and decides to do otherwise only when all its requirements are met. The method first checks whether the user has explicitly turned off client-side behavior by setting the EnableClientScript property to false . If the user has not turned off this behavior, the method retrieves the HttpBrowserCapabilities object and checks for the availability of ECMAScript 1.2 or later, Internet Explorer 5.5 or later, and the DHTML 4.0 DOM, all of which are needed to successfully use the control's editor UI implementation.

  • Overrides the OnPreRender method to call the DetermineRenderClientScript method. In a control's life cycle, OnPreRender is called after any user code that might change property values has executed, but before Render , where the control needs to know whether its client-side behavior has been activated. If the control's client-side behavior is enabled, the control needs to render script blocks within the page. The control does not render these script blocks itself because it needs to render them only once, even if the page contains multiple instances of the HtmlEditor control. Instead, the control calls the RegisterClientScriptBlock , RegisterStartupScript , and RegisterArrayDeclaration methods of its containing Page instance, and the page renders them as part of its own rendering logic.

  • Renders a reference to the script file, HtmlArea.js, by using a <script> tag. The control also renders the definition of the <h:HtmlArea> custom tag based on the behavior implemented in HtmlArea.htc by using the <?xml:namespace> and <?import> tags. As mentioned earlier, this reference and definition need to be rendered just once per given page, regardless of whether the page contains multiple instances of the server control. Furthermore, both the reference and the definition need to be rendered before any of the HTML is rendered by the control. Thus, the control uses the RegisterClientScriptBlock method of its containing Page instance. HtmlEditor uses its type name as the key value used to identify the script block so that the key will be constant across all instances of the control. You'll see these script files in a moment, when we look at the next set of code listings.

  • Contains a method named GetClientFileUrl that builds the URL used to refer to a client file. In an ideal world, this method would have been implemented by the Page class in ASP.NET, but because it isn't, we've provided the implementation in this sample control. In essence, the implementation retrieves the URL to the shared location created by ASP.NET that contains all client files from the machine-wide configuration setting. The implementation then proceeds to include the assembly name and version into the URL in accordance with the recommended scheme for deploying client files, which we described in the previous section, "Deploying Client Files." Finally, the implementation appends the path of the specific client file that needs to be referred to and returns the resulting URL. The resulting URL is a root-relative URL that can be referred to by any application running on the server. For example, the URL generated for HtmlArea.js is /asp ­net_client/mspress_servercontrols/1_0_0_0/HtmlArea.js .

  • In its Render method override, HtmlEditor checks its _render ­ClientScript field, which indicates whether its client-side behavior was activated during the PreRender phase. When its client-side behavior is disabled, the control simply delegates to the Render method of its base class, the TextBox control. When client-side behavior is enabled, HtmlEditor does not use any rendering functionality from its base class. The control first renders an invisible <textarea> tag. This tag contains the initial text within the HtmlEditor and it is used by client script to include the modified text as part of the data posted back to the server. The HtmlEditor simply reuses the postback data-handling logic built into its base class. This is possible because the name attribute of the hidden <textarea> is set to the UniqueID of the control, just as the base class would have done. The data contained in the hidden <textarea> is loaded by the TextBox into the control's Text property. Next, HtmlEditor renders a custom tag, <h:HtmlArea> , which contains the editing UI of the control. All the control's appearance- related properties are rendered on this custom tag. Notice that the Render method uses the ClientID property to render the id attribute and the identifier used in client event-handler script statements. HtmlEditor generates client script to track changes made to the HTML in the <h:HtmlArea> tag and update the value of the hidden <textarea> tag accordingly .

This book does not focus on client-side scripting. However, the client-side files used to implement the client-side behavior of the HtmlEditor control are provided in Listings 13-4 and 13-5 for completeness. Notice in Listing 13-4 that HtmlArea.js contains the methods called by the script that was rendered on the attributes and that was emitted via the RegisterStartupScript method. Also notice that the script references the array created by calls to RegisterArrayDeclaration .

Listing 13-4 HtmlArea.js
 functionha_OnHtmlChanged(htmlAreaElement,textAreaElement){ textAreaElement.value=htmlAreaElement.html; } functionha_InitializeElements(){ for(i=0;i<HtmlAreaList.length;i++){ varhtmlAreaElementID=HtmlAreaList[i]+"HtmlArea"; vartextAreaElementID=HtmlAreaList[i]; document.all[htmlAreaElementID].html= document.all[textAreaElementID].value; } } 
Listing 13-5 HtmlArea.htc
 <html> <head> <public:componenttagName="HtmlArea"> <public:defaultsviewLinkContent/> <public:defaultsviewInheritStyle="false"/> <public:propertyname="html"get="get_html"put="set_html"/> <public:eventid="htmlChangedEvent"name="onHtmlChanged"/> <public:attachevent="onContentReady"onevent="OnContentReady()"/> </public:component> <style> label,textarea,select{font-family:tahoma;font-size:8pt;} img.ToolBarButton{cursor:hand;height:22px;width:23px;} </style> <scriptlanguage="JavaScript"> functionget_html(){ if(viewHtmlCheckBox.checked==true){ returneditorTextArea.value; } 
 else{ returneditorDiv.innerHTML; } } functionset_html(data){ if(viewHtmlCheckBox.checked==true){ editorTextArea.value=data; } else{ editorDiv.innerHTML=data; } } functionDoEditorAction(actionString){ if(viewHtmlCheckBox.checked==true){ alert("EnableHTMLeditingbyuncheckingthe'ViewHTML Source'checkbox."); return; } if(actionString=='createlink'){ alert('['+editorDiv.createTextRange().text+']'); if(document.body.createTextRange().text==""){ return; } } document.execCommand(actionString); editorDiv.focus(); } functionDoEditorActionWithData(actionString,actionData){ if(viewHtmlCheckBox.checked==true){ alert("EnableHTMLeditingbyuncheckingthe'ViewHTML Source'checkbox."); return; }document.execCommand(actionString,false,actionData); editorDiv.focus(); } functionOnBlurEditorDiv(divElement){ htmlChangedEvent.fire(); } functionOnBlurEditorTextArea(textAreaElement){ htmlChangedEvent.fire(); } 
 functionOnContentReady(){ varurl=document.URL+"/../"; for(i=0;i<14;i++){ varsrcFile=document.all['i'+i].srcFile; document.all['i'+i].src=url+srcFile; } } functionOnClickShowHtmlCheckBox(checkBoxElement){ if(viewHtmlCheckBox.checked==true){ editorTextArea.value=editorDiv.innerHTML; editorDiv.style.display='none'; editorTextArea.style.display=''; editorTextArea.focus(); } else{ editorDiv.innerHTML=editorTextArea.value; editorTextArea.style.display='none'; editorDiv.style.display=''; editorDiv.focus(); } } functionOnClickToolBarButton(actionString){ DoEditorAction(actionString); } functionOnSelectedIndexChangedFontColorList(listElement){ DoEditorActionWithData('foreColor', listElement[listElement.selectedIndex].value); listElement.selectedIndex=0; } functionOnSelectedIndexChangedFontFaceList(listElement){ DoEditorActionWithData('fontName', listElement[listElement.selectedIndex].value); listElement.selectedIndex=0; } functionOnSelectedIndexChangedFontSizeList(listElement){ DoEditorActionWithData('fontSize', listElement[listElement.selectedIndex].value); listElement.selectedIndex=0; } 
 functionOnSelectedIndexChangedFormattingList(listElement){ if(listElement.selectedIndex==1){ DoEditorActionWithData('formatBlock','Normal'); DoEditorAction('removeFormat'); } else{ DoEditorActionWithData('formatBlock', listElement[listElement.selectedIndex].value); } listElement.selectedIndex=0; } </script> </head> <bodyunselectable="on"> <tableheight="100%"cellspacing="0"cellpadding="0"width="100%" border="0"> <trstyle="HEIGHT:22px">  <!--HTMLtogeneratetheToolBarUIsnippedout--> <textareaid="editorTextArea" onblur="OnBlurEditorTextArea(this)" style="display:none;width:100%;height:100%"> </textarea> <divid="editorDiv"onblur="OnBlurEditorDiv(this)" style="width:100%;height:100%;border:solid1px highlight;padding:4px;" contentEditable="true"></div> </td> </tr> </table> </body> </html> 

Listing 13-5 contains the definition of the <h:HtmlArea> custom tag and the JavaScript implementation used to create it. One way to think of custom tags implemented by using DHTML behaviors is to view them as the equivalent of user controls for client-side programming.

The HtmlEditor sample represents a very basic HTML editing control, and the implementation can be extended in several ways. One useful feature would be to allow the page developer to indicate whether the HTML entered by the user can contain script. This is especially important from a security perspective because enabling script entry would allow an unscrupulous user to enter potentially malicious scripts and this might be undesirable based on the intent of the application. Another useful addition to the control's features would be the ability to specify the actions and options available on its editing toolbars .



Developing Microsoft ASP. NET Server Controls and Components
Developing Microsoft ASP.NET Server Controls and Components (Pro-Developer)
ISBN: 0735615829
EAN: 2147483647
Year: 2005
Pages: 183

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