DragPanelExtender och ResizableControlExtender i FireFox 3

Idag på LIAn (sista veckan nu!) fick jag ta mig an att fixa några buggar vi upptäckt i FireFox 3 (som än så länge endast är släppt som release candidate). Problemen infann sig på en sida där man som administratör ska kunna flytta runt och ändra storlek på några element (DIVar) för att ställa in var de ska visas nånstans. Detta var förstås löst med Ajax Control Toolkit, närmare bestämt med extender-kontrollerna DragPanelExtender och ResizableControlExtender.

Det första problemet var att några utav de divar som skulle gå att flytta omkring la sig under en div som i själva verket bara hade som syfte att markera upp den yta varpå man kan placera de flyttbara divarna. Detta kunde man ganska enkelt lösa genom att kasta om div-arna i aspx-filen så att det som ska vara underst kommer först och det som ska vara överst kommer sist. Om du tänker dig att följande två Panels är absolutpositionerade så kommer den Panel som står sist i filen också att hamna överst;

<asp:Panel ID="pnlBackground" runat="server">
    <!-- Jag hamnar underst -->
</asp:Panel>

<asp:Panel ID="pnlDraggable" runat="server">
    <!-- Jag hamnar överst -->      
</asp:Panel>
      
<ajaxCTK:DragPanelExtender ID="dragpanel1" runat="server"
    TargetControlID="pnlDraggable" />
<ajaxCTK:ResizableControlExtender ID="resizable1" runat="server"
    TargetControlID="pnlDraggable"
    HandleCssClass="resizeHandle" />

Här ovan ser du även ajaxkontrollerna som kommer att göra det möjligt att flytta och ändra storlek på den sista Panel-kontrollen. Notera HandleCssClass i resize-kontrollen. Det andra problemet gällde nämligen just det här med att ändra storlek. I FireFox 3 gick det helt enkelt inte att få fatt och dra i resize-handtaget i nedre högra hörnet av varje div. När jag undersökte saken med hjälp av FireBug och jämförde mellan 2:an och 3:an upptäckte jag att inte heller FireBug registrerade att jag faktiskt valde "Inspect Element" på själva resize-handtaget. Så då kändes det ju logiskt att tro att även detta på något sätt hade hamnat "under" den omslutande diven. Mycket riktigt, genom att lägga till z-index i den klass som appliceras på resize-handtaget så fungerade det utmärkt:

    .resizeHandle
    {
        /.../
        z-index:5;
    }

Villkorlig validering måste hanteras både på klient- och serversidan!

Efter att ha läst MSDN-artikeln Asp.NET Validation in Depth blev jag tvungen att göra ett litet testcase för att se hur det fungerar om javascript är avstängt i webbläsaren. Jag gjorde följande HTML-sida:

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Conditional Validation</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />
    
    <div>
        <asp:CheckBox ID="CheckBox1" runat="server" />
        <asp:TextBox ID="TextBox1" runat="server" />
        <asp:RequiredFieldValidator runat="server" ID="RequiredFieldValidator1"
            ErrorMessage="RequiredFieldValidator"
            ControlToValidate="TextBox1"
            Enabled="false" />
        <asp:Button ID="Button1" runat="server" Text="Button" />
        <br /><br />
        Output: <asp:Label ID="lblOutput" runat="server" Text="Label" />
    </div>
    </form>

    <script type="text/javascript">
        
        
        
    </script>
</body>
</html>

Här har vi alltså en checkbox, en textbox, en requiredFieldValidator och en knapp. Notera att validatorns Enabled-property är satt till false, detta eftersom jag vill validera textboxen endast om checkboxen är kryssad och det kommer den inte att vara när sidan laddas. Slutligen har vi en label som får sitt värde i sidans Page_Load-funktion:

    protected void Page_Load(object sender, EventArgs e)
    {
        lblOutput.Text = string.Format("Postback = '{0}', Checkbox checked = '{1}', Textbox text = '{2}'",
            IsPostBack.ToString(),
            CheckBox1.Checked.ToString(),
            TextBox1.Text);
    }

För att valideringskontrollen ska aktiveras när kryssrutan är ikryssad lägger jag till följande javascript i sidan:

        //<![CDATA[
        function pageLoad() 
        {
            $addHandler($get('<%=CheckBox1.ClientID %>'), 'click', CheckBox1_Click)
        }
        
        function CheckBox1_Click(e)
        {
            var validator = $get('<%=RequiredFieldValidator1.ClientID %>');
            ValidatorEnable(validator, e.target.checked);
            validator.style.visibility='hidden';
        }
        //]]>

För att den här javascript-koden ska fungera krävs att vi har en ScriptManager på sidan (rad 11 i html-koden), den ger mig tillgång till att använda Asp.NET AJAX-genvägar som $get och $addHandler. För att jag inte ska råka anropa dessa funktioner innan Asp.NET AJAX-scripten är ordentligt laddade använder jag mig av funktionen pageLoad. Den körs vid sidladdning, men först när alla AJAX-script är ordentligt laddade. I pageLoad lägger jag till en eventhandler för checkboxen så att funktionen CheckBox1_Click körs när man klickar i checkboxen. I CheckBox1_Click gör jag först en referens till min valideringskontroll och därefter anropar jag en funktion som heter ValidatorEnable. Det är en javascript-funktion som kommer att finnas tillgänglig i sidan om sidan innehåller en eller flera Asp.NET-valideringskontroller (i det här fallet en RequuriedFieldValidator). ValidatorEnable tar två argument, det första ska vara en referens till en valideringskontroll och det andra ska vara en boolean som avgör om valideringskontrollen blir enabled (true) eller disabled (false). För att koppla detta till om checkboxen är kryssad eller inte skickar jag helt enkelt in kryssrutans checked-egenskap som värde (e är referens till eventet, target är referens till det element som triggade eventet). Förutom att ValidatorEnable enablar eller disablar valideringskontrollen kör den också själva valideringen och visar felmeddelandet om valideringen inte går igenom. Detta blir lite konstigt om man först kryssar i checkboxen innan man hunnit skriva i något i textboxen. Detta åtgärdar jag genom att sist i funktionen dölja valideringskontrollen, den kommer att visas igen om jag klickar på knappen utan att fylla i textboxen.

Nu fungerar detta alldeles utmärkt så länge webbläsaren vi kollar med har javascript aktiverat. Men vad händer om javascript inte är aktiverat? Ja, då går det alldeles utmärkt att posta sidan även om checkboxen är kryssad samtidigt som textboxen är tom. Lösningen på problemet, som jag alltså uppmärksammades på här, är att i code-behind göra en override på Page-klassens Validate-funktion enligt följande:

    public override void Validate()
    {
        RequiredFieldValidator1.Enabled = CheckBox1.Checked;
        base.Validate();
    }

Denna funktion kommer nu alltså att anropas innan den ordinarie valideringsfunktionen drar igång (via base.Validate()) och på så sätt kan vi se till att vi även på serversidan enablar och disablar olika valideringskontroller beroende på hur formuläret är ifyllt. Mer om kopplingarna mellan Asp.NET-valideringskontrollerna och Page-klassen kan du alltså läsa här.

Else eller ?:

Fredrik Normén slänger ut en fråga angående valet mellan if/else och ?:-operatorn. Intressanta kommentarer som jag i stor utsträckning håller med om; vid mer komplicerade situationer känns if/else bra och vid enklare tilldelningar är ?: smidig även om jag inte riktigt vant mig vid syntaxen ännu och ibland får skriva satsen i if/else-variant först. ;-) Solklart exempel på när jag skulle välja ?:-operatorn;

//Med if/else
if (condition)
{
    result = "Villkoret är sant";
}
else
{
    result = "Villkoret är falskt";
}

//Med ?:-operatorn
result = condition ? "Villkoret är sant" : "Villkoret är falskt";

Denna operator fungerar exakt likadant även i Javascript och där kanske man mer ofta än i C# har anledning att välja den framför if/else för att minimera storleken på sina script som ju behöver laddas ner till klienten/webbläsaren.

Intressant artikel angående accesskeys

Tillgängliga hemsidor tycker jag är ett intressant ämne och när jag idag satt och lekte lite med accesskey-attributet i html och inte fick det att fungera i FireFox snubblar jag på följande intressanta information; Using Accesskeys - Is it worth it?

Enligt Wikipedia kommer accesskey-attributet att vara deprecated i XHTML2 där man kommer implementera något som kallas XHTML Role Access Module. Hittar dock ingen riktigt bra information om när XHTML2 kommer stödjas av webbläsare och så dyker det upp en del googleträffar där begreppet HTML5 förekommer... Ytterligare en grej att sätta sig in i nån gång... ;-) Någon som har nåt bra lästips?

På temat tillgänglighet i kombination med Asp.NET kan jag också rekommendera msdn-artikeln Building ASP.NET 2.0 Web Sites Using Web Standards. Den har några år på nacken och avsnittet om accesskey kan du väl hoppa över då, men den är ändå läsvärd om inte annat så för bakgrundsinformation kring DOCTYPE, webstandards med mera.

Lite fix med SyntaxHighlighter

OBS! Numera använder jag Windows Live Writer och pluginprogrammet paste from Visual Studio för att publicera kod på bloggen. Syntaxhighlighter-scripten är borttagna och de kodexempel som publicerats så kommer helt enkelt visas i Courier New utan färgformatering av keywords etc. /Dan, 081211

Till denna blogg har jag valt SyntaxHighlighter för att skriva ut kod på ett läsvänligt sätt. Den fungerar som så att den kod jag vill visa upp på sidan omsluts med en pre-tagg där name="code" och class sätts till ett alias för det "språk" jag vill använda. SyntaxHighlighter har redan stöd för språk som C#, CSS, SQL och XHTML och vill man kan man med lite regexp-erfarenheter ganska enkelt lägga till stöd för flera språk. När man väl implementerat SyntaxHighlighters-scriptet på sin sida kommer det att gå igenom alla pre-taggar med name="code" och modifera till det så att man får ett "kodfönster" enligt följande, här med exempel på hur pre-taggen för SyntaxHiglighter ska se ut:

<pre name="code" class="html">
    Lite html-kod <b>här</b>.
</pre>

Några saker behöver dock fixas. Alla <-tecken måste ersättas med &lt; och man bör göra detta även med &- och "-tecknen, dvs ersätta med &amp; och &quot;. För att slippa göra detta manuellt varje gång jag ska publicera lite kod bestämde jag mig för att göra en liten "widget"-lösning som fixar detta åt mig. Jag gjorde två html-sidor, den första bygger upp ett frameset som laddar in Blogger i en frame och min andra html-sida i en annan frame;

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>deap Blogger Editor</title>
    </head>

    <frameset cols="*,500">
        <frame src="http://www.blogger.com" name="blogger">
        <frame src="snippets.html" name="snippets">
    </frameset>
</html>

Sidan snippets.html ser ut så här:

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Snippets</title>
    </head>
    <body>

        <input id="target" type="text" value=' target="_blank"' onclick="javascript:this.select();" readonly="readonly" />

        <br /><br />

        <textarea id="replacer" style="width:450px;height:400px;" wrap="off"></textarea>
        <br />
        <select id="language">
            <option value="c#">C#</option>
            <option value="css">CSS</option>
            <option value="js">JavaScript</option>
            <option value="sql">SQL</option>
            <option value="xml">XML/HTML</option>
        </select>
        <input type="button" value='Fix tabs and replace &, < and "' onclick="javascript:doReplace();" />

    </body>

<script type="text/javascript">
    //<![CDATA[
    //Funktionen doReplace infogas här.
    //]]>
</script>

</html>

Vad har vi här då? Först ett litet sidospår; Först i bodyn kommer en text input med texten target="_blank". Den har jag lagt in för att jag lätt ska kunna fixa så att länkar jag lägger in i mina bloggposter ska öppnas i nytt fönster, det är tyvärr inte standard i bloggers redigerare. Textboxen fungerar precis som när man ska kopiera embed-kod från youtube genom attributet readonly och onclick-eventet som markerar all text så fort man klickar på textboxen. Tillbaka till SyntaxHighlighter. Efter detta kommer nämligen en textarea, en dropdownmeny och en knapp. I textarean klistrar jag in den kod jag vill publicera exakt så som jag har den i Visual Studio, därefter väljer jag språk i dropdownmenyn och klickar på knappen. Knappen triggar javascriptfunktionen som jag lagt in längst ner i sidan, koden kommer här:

function doReplace()
{
    var outputString;

    outputString = document.getElementById('replacer').value.replace(/\t/g, '    ');
    outputString = outputString.replace(/&/g, '&amp;');
    outputString = outputString.replace(/\</g, '&lt;');
    outputString = outputString.replace(/"/g, '&quot;');

    var language = document.getElementById('language').value;
    document.getElementById('replacer').value = '<pre name="code" class="' + language + '">\n' + outputString + '\n</pre>';
    document.getElementById('replacer').select();
}

Funktionen plockar in värdet från textarean och ersättar tabbar med fyra mellanslag, <-tecknet med &lt;, &-tecknet med &amp; och "-tecknet med &quot;. Dessutom infogar den pre-taggen som krävs för att SyntaxHiglighter ska visa koden på sitt fina vis och det språk jag valt i dropdownmenyn infogas som värde på class-attributet. Slutligen markeras all text i textarean med select()-kommandot och sen är det bara för mig att kopiera och klistra in i Bloggers textredigerare. Syntaxen för Javascripts replace-funktion ser kanske lite märklig ut men det visade sig vara så att första argumentet är en RegularExpression och det andra argumentet är den sträng man vill ersätta med. Jag är ingen expert på RegularExpression utan fick god hjälp av w3Schools när det gäller detta och kom fram till att själva uttrycket ska omges av /-tecken och om jag vill ersätta alla förekomster av nånting lägger jag till ett g för att det ska bli en "global search", annars ersätts bara första förekomsten. För <-tecknet var jag tvungen att lägga in en escape character \ då <-tecknet har andra betydelser i Regular Expressions. \t står för tabb-tecknet. Min båda html-filer har jag än så länge på min hårddisk och snabblänken till blogger i min webbläsare går nu till framesetfilen istället så att varje gång jag ska blogga automatiskt får med dig detta funktionsfönster. Nån gång i framtiden kommer jag säkert flytta ut dessa filer på nätet så att jag kan använda funktionaliteten även när jag bloggar från en annan dator.

Skärmdump:

Uppdatering: Har under kvällen studerat SyntaxHighlighter-koden lite mer och upptäcker att det är inbyggt att tabbar ersätts med mellanslag. Dock verkar det i mitt fall vara så att om jag sparar tabbar i Bloggers textredigerare så ersätter blogger dessa med ett mellanslag. Så min tabfix enligt ovan behövs ändå, i just detta fall. En annan bra grej jag upptäckte är att SyntaxHighlighter automatiskt tar bort onödig indentering, så om jag kopierar ut t.ex. en if-else-sats som kanske ligger i ett namespace, i en klass, i en metod så behöver jag inte "avindentera" det själv för att det ska se bra ut på bloggen.

Snart dags för objektorienterad analys och design

BokomslagEfter 15 veckors LIA (Lärande I Arbete, praktik) är det den 2 juni dags för skolbänken igen och då är det dags för en tre veckor lång kurs i Objektorienterad analys och design. I måndags kom mejl om vilken bok vi ska ha :-). Känner mig faktiskt lite förberedd eftersom jag den gångna veckan haft lite av den typen av uppgifter på LIAn. Har gjort lite databasdesign m.m. och tror iaf att det faller lite inom detta område. Ska bli intressant och det känns som det ska bli hur kul som helst att komma tillbaka till skolan :-).

Rätt CDATA-syntax för javascript

När man vill infoga ett javascript i sin html-sida och samtidigt följa XHTML-standard måste man innesluta scriptet i en CDATA-sektion för att XHTML-parsningen ska strunta i scriptkoden (den är ju inte giltig XML). Syntaxen för en CDATA-sektion i XML är att den inleds med <![CDATA[ och avslutas med ]]>. För att få det rätt i javascript-sammanhang måste man lägga till javascripts kommentars-syntax // för att just dessa rader inte ska tolkas som javascript. Slutresultatet blir alltså så här:

<script type="text/javascript">
//<![CDATA[
alert("Här ska scriptet vara.");
//]]>
<script>

Om man förbiser att kommentera bort CDATA-syntaxen får man ett javascriptfel, Firebug ger följande kryptiska felmeddelande: Syntax error <![CDATA[\n. Det här lärde jag mig den hårda vägen när jag i dagarna skulle implementera SyntaxHighlighter här på denna blogg. Bloggers HTML-malls-redigerare kräver "well-formed XHTML" så den säger ifrån om något är fel i det avseendet. Men det gick utmärkt att posta mina ändringar utan de små javascript-snedstrecken för kommentarer // vid CDATA-start och -slut... Istället för att inse att <![CDATA[ självfallet inte är giltig javascript hängde jag upp mig på det där \n som Firebug infogat i felmeddelandet. Kunde det vara så att Blogger inte tillät "inline" javascript, ska jag bli tvungen att länka in en separat .js-fil som då måste hostas nån annanstans? Till slut snubblade jag i alla fall över lösningen och sen hittade jag också denna sida som beskriver CDATA-syntaxen för javascript på ett ypperligt sätt (på engelska).