HtmlTextBox jako User Control v ASP.NET

26. února 2004

O vizuální editaci textu formátovaného pomocí HTML přímo ve stránce jsme psali už mnohokrát. Tyto vědomosti využijeme k sestavení uživatelského ovládacího prvku, kterým můžeme snadno nahradit vestavěný TextBox tam, kde potřebujeme uživateli dát k dispozici možnost psát formátovaný text.

Výsledkem našeho snažení bude uživatelský ovládací prvek podobný jako ICQ Pager – zapouzdříme do něj funkcionalitu zde již popsaného DHTML editoru pro IE, přihlédneme i ke zvláštnostem řešení, které dává k dispozici poslední verze Mozilly. Jenom zopakuji, že řešení vychází z popisu povelů metody execCommand. Kompatibilitu s jinými prohlížeči v tomto prvku řešit nebudeme, zde vytvořený prvek tedy bude plně funkční pouze v Internet Exploreru se zapnutým JavaScriptem, některé funkce budou pracovat i v Mozille. Prohlédněte si ukázku (zdrojový kód).

Nejprve ukázková stránka s HtmlTextBoxem:

 <%@ Page Language=“C#“ Trace=“False“ EnableSessionState=“False“ ValidateRequest=“False“ Debug=“False“ %>
<%@Register TagPrefix=“myasp“ TagName=“HtmlTextBox“ Src=“HtmlTextBox.ascx“ %>
<script language=“C#“ runat=“server“>
void Button_Click(Object sender, EventArgs e)
{
  txbHtmlOut.Text = htbEditor1.Text;
}
private void Page_Load(object sender, System.EventArgs e)
{
  if (!IsPostBack)
    htbEditor1.EnableViewSource = true;
}
</script><?xml version=“1.0″ encoding=“utf-8″ ?>
<!DOCTYPE html PUBLIC „-//W3C//DTD XHTML 1.0 Transitional//EN“ „http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd“>
<html xmlns=“http://www.w3.org/1999/xhtml“ xml:lang=“cs-CZ“ lang=“cs-CZ“ dir=“ltr“>
  <head>
    <meta http-equiv=“Content-type“ content=“text/html; charset=utf-8″ />
    <title>HtmlTextBox</title>
  </head>
  <body>
    <form runat=“server“>
      <asp:TextBox Id=“txbHtmlOut“ TextMode=“MultiLine“ Rows=“5″ Columns=“40″ RunAt=“server“ />
      <br /><br />
      <myasp:HtmlTextBox id=“htbEditor1″ BackColor=“#FFFFFF“ FontName=“Arial“ FontSize=“12px“ RunAt=“server“ />
      <br />
      <asp:Button RunAt=“server“ Text=“Odeslat“ onclick=“Button_Click“ />
    </form>
  </body>
</html>

Formulář obsahuje TextBox, který je využit pouze pro zobrazení výsledného kódu odeslaného z našeho prvku HtmlTextBox. V části skriptu je ukázka programového nastavení vlastnosti EnableViewSource při prvním načtení stránky – v obsluze události Page_Load. Obsluha Button_Click zajistí převzetí obsahu našeho prvku a naplnění jeho obsahu do TextBoxu. V direktivách stránky vidíme zaregistrování uživatelského ovládacího prvku a zvláště bych chtěl upozornit na potřebnou deaktivaci hlídání nebezpečných znaků v příchozích požadavcích na aplikaci. Tato kontrola je ve výchozím stavu v ASP.NET od verze 1.1 zapnutá a bránila by nám přijmout přímo HTML kód. Kontrola je tedy deaktivována nastavením ValidateRequest=“False“, je proto velmi důležité samostatně si ohlídat potenciálně nebezpečné znaky v aplikaci, aby se předešlo možnému cross site scripting, případně sql injection.

Následuje kód ovládacího prvku:

<%@ Control Language=“C#“ %>
<%@ Import Namespace=“System.Drawing“ %>
<script language=“C#“ runat=“server“>
public String Text
{
  set { HTMLTEXTBOX.Attributes[„value“] = value; }
  get
  {
    if (HTMLTEXTBOX.Attributes[„value“] != null)
      return HTMLTEXTBOX.Attributes[„value“];
    else
      return String.Empty;
  }
}
public String ToolTip
{
  set { pnlHtmlTextBox.ToolTip = value; }
  get { return pnlHtmlTextBox.ToolTip; }
}
public Unit Width
{
  set { pnlHtmlTextBox.Width = value; }
  get { return pnlHtmlTextBox.Width; }
}
public Unit Height
{
  set { ltrHeight.Text = value.ToString(); }
  get { return Unit.Parse(ltrHeight.Text); }
}
public Color ForeColor
{
  set { ltrForeColor.Text = ColorTranslator.ToHtml(value); }
  get { return ColorTranslator.FromHtml(ltrForeColor.Text); }
}
public Color MenuColor
{
  set { pnlHtmlTextBox.BackColor = value; }
  get { return pnlHtmlTextBox.BackColor; }
}
public Color BackColor
{
  set { ltrBackColor.Text = ColorTranslator.ToHtml(value); }
  get { return ColorTranslator.FromHtml(ltrBackColor.Text); }
}
public Unit FontSize
{
  set { ltrFontSize.Text = value.ToString(); }
  get { return Unit.Parse(ltrFontSize.Text); }
}
public String FontName
{
  set { ltrFontName.Text = value; }
  get { return ltrFontName.Text; }
}
public Boolean EnableViewSource
{
  get { return imgViewSource.Visible; }
  set { imgViewSource.Visible = value; }
}
public Boolean EnableSaveAs
{
  get { return imgSaveAs.Visible; }
  set { imgSaveAs.Visible = value; }
}
.
.
.
public Boolean EnableNotePad
{
  get { return imgNotePad.Visible; }
  set { imgNotePad.Visible = value; }
}
void Page_Load(object sender, System.EventArgs e)
{
  ltrHtmlBox.Text = HTMLTEXTBOX.ClientID;
}
</script>
<link rel=“stylesheet“ media=“screen“ type=“text/css“ href=“HtmlTextBox/HtmlTextBox.css“ />
<input type=“hidden“ id=“HTMLTEXTBOX“ value=““ runat=“server“ />
<script type=“text/javascript“>
  <!– <![CDATA[
  var rtfObject = document.getElementById(‚<asp:Literal Id=“ltrHtmlBox“ RunAt=“server“ />‘);
  var editorObject;
  var IsHtmlText = false;
  function EditorStart()
  {
    if (editorObject == null)
    {
      editorObject = document.getElementById(‚HtmlTextBox‘).contentWindow;
      try
      {
        editorObject.document.designMode=’on‘;
      }
      catch (e)
      {
        alert(‚Váš prohlížeč bohužel nepodporuje funkci editoru…‘);
      }
    }
    else
    {
      editorObject.document.body.style.background='<asp:Literal Id=“ltrBackColor“ RunAt=“server“ />‘;
      editorObject.document.body.style.color='<asp:Literal Id=“ltrForeColor“ RunAt=“server“ />‘;
      editorObject.document.body.style.fontFamily='<asp:Literal Id=“ltrFontName“ RunAt=“server“ />‘;
      editorObject.document.body.style.fontSize='<asp:Literal Id=“ltrFontSize“ RunAt=“server“ />‘;
      editorObject.document.body.style.fontWeight='<asp:Literal Id=“ltrFontBold“ Text=“bold“ Visible=“false“ RunAt=“server“ />‘;
        if (rtfObject.value.length > 0)
        {
          editorObject.focus();
          editorObject.document.selection.createRange().pasteHTML(rtfObject.value);
        }
        document.getElementById(‚HtmlTextBox‘).onbeforedeactivate = SendData;
    }
  }
  //]]> –>
</script>
<script type=“text/javascript“ src=“HtmlTextBox/HtmlTextBoxFunctions.js“></script>
<asp:Panel id=“pnlHtmlTextBox“ BackColor=“Menu“ Width=“100%“ style=“padding:2px;“ BorderStyle=“outset“ BorderWidth=“2px“ RunAt=“server“>
  <div>
    <span class=“btnPanel“>
      <asp:Image ToolTip=“Režim HTML“ Id=“imgViewSource“ onclick=“SwapModes(this);“ ImageUrl=“HtmlTextBox/Images/Html.gif“ CssCssClass=“imgBtn“ unselectable=“on“ onmouseover=“className=’imgBtnAct‘;“ onmouseout=“className=’imgBtn‘;“ RunAt=“server“ />
      <asp:Image ToolTip=“Uložit jako soubor“ Id=“imgSaveAs“ onclick=“DoCommand(‚SaveAs‘,true,’Pøíspìvek.html‘);“ ImageUrl=“HtmlTextBox/Images/Save.gif“ CssClass=“imgBtn“ unselectable=“on“ onmouseover=“className=’imgBtnAct‘;“ onmouseout=“className=’imgBtn‘;“ RunAt=“server“ />
      <asp:Image ToolTip=“Tisk (CTRL-P)“ Id=“imgPrint“ onclick=“DoCommand(‚Print‘,true,null);“ ImageUrl=“HtmlTextBox/Images/Print.gif“ CssClass=“imgBtn“ unselectable=“on“ onmouseover=“className=’imgBtnAct‘;“ onmouseout=“className=’imgBtn‘;“ RunAt=“server“ />
      <img src=“HtmlTextBox/Images/Separator.gif“ class=“separator“ unselectable=“on“ alt=““ />
    </span>
.
.
.
    <span class=“btnPanel“>
      <asp:Image ToolTip=“Poznámkový blok“ Id=“imgNotePad“ onclick=“NotePad();“ ImageUrl=“HtmlTextBox/Images/Notepad.gif“ CssClass=“imgBtn“ unselectable=“on“ onmouseover=“className=’imgBtnAct‘;“ onmouseout=“className=’imgBtn‘;“ RunAt=“server“ />
      <img src=“HtmlTextBox/Images/Separator.gif“ class=“separator“ unselectable=“on“ alt=““ />
    </span>
  </div>
  <iframe style=“width:100%; height:<asp:Literal Id=“ltrHeight“ Text=“400px“ RunAt=“server“ />;“ onload=“EditorStart();“ id=“HtmlTextBox“ src=“HtmlTextBox/Empty.html“></iframe>
</asp:Panel>

Převážnou část kódu tvoří obrázky v liště nástrojů a dále definice veřejných vlastností. Základem editoru je iframe, který se skriptem přepne do režimu editace. K němu jsou přidány obslužné funkce – obrázková tlačítka. Každé z tlačítek má vlastnost, kterou můžeme tlačítko a tím i určitou funkci zakázat nebo povolit. Další vlastnosti slouží pro definici vzhledu prvku ve stránce – vlastností je opravdu hodně, jejich popis by jen ještě více nafouknul tento článek, proto zájemce odkazuji na zdrojový kód, kde jednotlivé vlastnosti snadno nalezne. Názvy i typy jsem volil tak, aby byly intuitivní a naším novým prvkem šel vcelku bez problémů ihned nahradit stávající TextBox.

Obsah editovaného iframe je jako u předchozího editoru předáván ve skrytém poli, v naší aplikaci je toto pole doplněno atributem RunAt=“server“, čímž si zajistíme nejen snadný přístup k hodnotě pole v aplikaci, ale také automatické udržení hodnoty i při odesílání stránky (takzvaného postbacku) prostřednictvím ViewState.

Všimněte si také použití nestandardních atributů unselectable – tyto se hodí, aby obrázková tlačítka při kliknutí myší nezískávala zaměření (focus) a uživateli se tak „neztrácel“ kurzor z editačního pole. Vzhled editoru je definován v externím souboru stylů (odkazuji na již zmíněný článek nebo na zdrojový kód), funkce jednotlivých tlačítek jsou definovány v externím souboru JavaScriptu.

  function SwapModes(btnObj)
  {
    editorObject.focus();
    if (!IsHtmlText)
    {
      editorObject.document.body.innerText = editorObject.document.body.innerHTML;
      btnObj.title = „Režim WYSIWYG“;
      btnObj.src = „HtmlTextBox/Images/Wysiwyg.gif“;
      document.getElementById(‚pnlFunctions‘).style.display = „none“;
      IsHtmlText = true;
    }
    else
    {
      editorObject.document.body.innerHTML = editorObject.document.body.innerText;
      btnObj.title = „Režim HTML“;
      btnObj.src = „HtmlTextBox/Images/Html.gif“;
      document.getElementById(‚pnlFunctions‘).style.display = „“;
      IsHtmlText = false;
    }
  }
  function SendData()
  {
    if (IsHtmlText)
    {
      rtfObject.value = editorObject.document.body.innerText;
    }
    else
      rtfObject.value = editorObject.document.body.innerHTML;
  }
  function DoCommand(x,y,z)
  {
    editorObject.focus();
    editorObject.document.execCommand(x,y,z);
  }
  function PastePlain()
  {
    var strText = document.createElement(„BODY“);
    var strClipbrdData = editorObject.clipboardData.getData(„TEXT“);
    strText.innerHTML = strClipbrdData;
    editorObject.focus()
    editorObject.document.selection.createRange().pasteHTML(strText.innerText);
  }
  function AddTag(tag)
  {
    var strText = editorObject.document.selection.createRange().text;
    editorObject.focus();
    if ( strText != „“ )
      editorObject.document.selection.createRange().pasteHTML(‚<‚ + tag + ‚>‘ + strText + ‚</‘ + tag + ‚>‘);
  }
  function Vertical()
  {
    var strText = editorObject.document.selection.createRange().text;
    editorObject.focus();
    if ( strText !=““ && strText != „http://“ && strText != null )
      editorObject.document.selection.createRange().pasteHTML(‚<span style=“writing-mode:tb-rl;“>‘ + strText + ‚</span>‘);
  }
  function Image()
  {
    var strUrl = prompt(„Zadejte URL obrázku“,“http://“);
    editorObject.focus();
    if ( strUrl !=““ && strUrl != „http://“ && strUrl != null )
      DoCommand(‚InsertImage‘,false,strUrl);
  }
  function BgSound()
  {
    var soundUrl = prompt(„Zadejte URL zvuku na pozadí“,“http://“);
    var soundLoop = prompt(„Počet opakování zvuku? (-1 pro nekonečné opakování)“,“-1″);
    editorObject.focus();
    if ( soundUrl !=““ && soundUrl != „http://“ && soundUrl != null && soundLoop !=““ && soundLoop != „http://“ && soundLoop != null)
      editorObject.document.selection.createRange().pasteHTML(‚<bgsound src=“‚ + soundUrl + ‚“ loop=“‚ + parseInt(soundLoop) + ‚“>&#9834;</bgsound>‘);
  }
  function Marquee()
  {
    var strText = prompt(„Zadejte běžící text“,““);
    var soundLoop = prompt(„Počet opakování? (-1 pro nekonečné opakování)“,“-1″);
    editorObject.focus();
    if ( strText !=““ && strText != „http://“ && strText != null && soundLoop !=““ && soundLoop != „http://“ && soundLoop != null)
      editorObject.document.selection.createRange().pasteHTML(‚<marquee truespeed=“true“ scrollamount=“2″ scrolldelay=“31″ loop=“‚ + parseInt(soundLoop) + ‚“>‘ + strText + ‚</marquee>‘);
  }
  function Media()
  {
    var strUrl = prompt(„Zadejte URL multimediálního obsahu“,“http://“);
    editorObject.focus();
    if ( strUrl !=““ && strUrl != „http://“ && strUrl != null )
      editorObject.document.selection.createRange().pasteHTML(‚<embed type=“application/x-mplayer2″ pluginspage=“http://www.microsoft.com/Windows/MediaPlayer/“ src=“‚ + strUrl +'“ filename=“‚ + strUrl +'“ movie=“‚ + strUrl +'“ allowchangedisplaysize=“0″ autosize=“1″ autorewind=“1″ autostart=“1″ enabletracker=“1″ showcontrols=“1″ showstatusbar=“1″ showtracker=“1″></embed>‘);
  }
  function Mail()
  {
    var strUrl = prompt(„Zadejte emailovou adresu“,“@“);
    editorObject.focus();
    if ( strUrl !=““ && strUrl != „@“ && strUrl != null )
      DoCommand(‚CreateLink‘,false,’mailto:‘ + strUrl);
  }
  function NotePad()
  {
    window.open(„view-source:file:///Nový.txt“, „“, „“);
  }
  function SetForeColor()
  {
    colValue = window.showModalDialog(„HtmlTextBox/Dialog_color.html“, „“, „help:no; center:yes; dialogWidth:245px; dialogHeight:450px; border:thin; resizable:0; status:no; scrollbars:0; fullscreen:0“);
    if (colValue[0] != „“)
      DoCommand(„ForeColor“, false, colValue[0].substr(1, 7));
  }
  function SetBackColor()
  {
    colValue = window.showModalDialog(„HtmlTextBox/Dialog_color.html“, „“, „help:no; center:yes; dialogWidth:245px; dialogHeight:450px; border:thin; resizable:0; status:no; scrollbars:0; fullscreen:0“);
    if (colValue[0] != „“)
      DoCommand(„BackColor“, false, colValue[0].substr(1, 7));
  }
  function SetFont(fieldObj)
  {
    if (fieldObj.value != ‚default‘)
      DoCommand(‚FontName‘,false,fieldObj.value);
    else
      DoCommand(‚RemoveFormat‘);
    fieldObj.selectedIndex=0;
  }
  function SetSize(fieldObj)
  {
    if (fieldObj.value != ‚default‘)
      DoCommand(‚FontSize‘,false,fieldObj.value);
    else
      DoCommand(‚RemoveFormat‘);
    fieldObj.selectedIndex=0;
  }

Funkce nemá cenu opakovaně pitvat, vycházejí z již zmíněného DHTML editoru. V každém případě jde o velmi triviální přepis – mnohem čistější by bylo původní editor přepsat jako serverový ovládací prvek – jak vidíme, náš prvek nebude funkční, pokud jej použijeme ve stránce vícekrát. Pokud jej chceme použít v jiné aplikaci, musíme nakopírovat do její složky všechny potřebné soubory, což je daň za jednoduše vytvořený user control (v podstatě by se totiž user control pro tento typ funkčnosti, kterou lze požadovat ve více aplikacích, neměl používat), berme proto naši ukázku jako první počin při přepracování nějaké starší aplikace například z PHP.

Ještě bych doplnil, že uživatelský ovládací prvek je zařazen do prostoru názvů ASP (v některých případech _ASP), tento je však obyčejně importován implicitně. Pokud bychom však potřebovali přetypovat nějaký objekt například při použití našeho editoru v DataGridu, je dobré vědět, že ve stránce bude k dispozici jako typ HtmlTextBox_ascx, čili přetypování by mohlo vypadat třeba takto:

HtmlTextBox_ascx txtLegend = (HtmlTextBox_ascx) e.Item.Cells[1].Controls[1];

Závěrem jen dodám, že ve výchozím nastavení prvku jsou k dispozici všechna tlačítka. Pokud chceme v aplikaci některé funkce znepřístupnit (například barvu písma nebo pozadí), je potřeba nastavit vlastnost odpovídajícího tlačítka na false, ať už programově nebo atributem v elementu prvku – například EnableViewSource=“False“. Seznam všech vlastností získáte snadno pohledem do zdrojového kódu prvku.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Další článek jarin
Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *