Putting It Together ”The HtmlEditor ExampleIn 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. 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> <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"/> Listing 13-3 contains the code for the HtmlEditor control. Listing 13-3 HtmlEditor.csusingSystem; 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:
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.jsfunctionha_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"> 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 . ![]() |