Tuesday, April 15, 2008

Using #region Directive With JavaScript Files in Visual Studio

Suppose how nice it would be to use #region directive when writing javascript code.

Javascript is becoming more and more complicated, and it is hard to scroll up and down every time we want to find needed function name and see what parameters it accepts.

I was so tired with this scrolling that started to search some alternative small IDE-s or highlighting text editors which would allow me to use collapsible regions in js files, but in this case I would lose javascript intellisense in VS 2008. I even thought about creating my own text editor plug-in for VS, but again - what about intellisense.

Earlier or later, I found one nice solution to the problem on Microsoft forums. Yes - it's so simple. The solution uses simple macros which parses current document and creates collapsible regions for

//#region

//#endregion

pairs.

So simple :)

Thanks to the author!

And because I don't not find it to be easily detectable in the internet, I decided to blog this.

 

Just do the following steps to support //#region directive:

1) Open Macro explorer:

image

2) Create new macro:

image

3) Name it "OutlineRegions":

image

4) Click "Edit" macro and paste the following VB code into it:

Option Strict Off
Option Explicit Off

Imports System
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics
Imports System.Collections

Public Module JsMacros

    Sub OutlineRegions()
        Dim selection As EnvDTE.TextSelection = DTE.ActiveDocument.Selection

        Const REGION_START As String = "//#region"
        Const REGION_END As String = "//#endregion"

        selection.SelectAll()
        Dim text As String = selection.Text
        selection.StartOfDocument(True)

        Dim startIndex As Integer
        Dim endIndex As Integer
        Dim lastIndex As Integer = 0
        Dim startRegions As Stack = New Stack()

        Do
            startIndex = text.IndexOf(REGION_START, lastIndex)
            endIndex = text.IndexOf(REGION_END, lastIndex)

            If startIndex = -1 AndAlso endIndex = -1 Then
                Exit Do
            End If

            If startIndex <> -1 AndAlso startIndex < endIndex Then
                startRegions.Push(startIndex)
                lastIndex = startIndex + 1
            Else
                ' Outline region ...
                selection.MoveToLineAndOffset(CalcLineNumber(text, CInt(startRegions.Pop())), 1)
                selection.MoveToLineAndOffset(CalcLineNumber(text, endIndex) + 1, 1, True)
                selection.OutlineSection()

                lastIndex = endIndex + 1
            End If
        Loop

        selection.StartOfDocument()
    End Sub

    Private Function CalcLineNumber(ByVal text As String, ByVal index As Integer)
        Dim lineNumber As Integer = 1
        Dim i As Integer = 0

        While i < index
            If text.Chars(i) = vbCr Then
                lineNumber += 1
                i += 1
            End If

            i += 1
        End While

        Return lineNumber
    End Function

End Module

5) Save the macro and close the editor.

6) Now let's assign shortcut to the macro. Go to Tools->Options->Environment->Keyboard and search for your macro in "show commands containing" textbox:

image

7) now in textbox under the "Press shortcut keys" you can enter the desired shortcut. I use Ctrl+M+E. I don't know why - I just entered it first time and use it now :)

 

That's it, now if you write the following javascript code:

/// <reference name="MicrosoftAjax.debug.js" />
/// <reference name="MicrosoftAjaxTimer.debug.js" />
/// <reference name="MicrosoftAjaxWebForms.debug.js" />

// KWB.BaseWizard class constructor
//#region
Type.registerNamespace("KWB");
KWB.BaseWizard = function(field) {
    // call base constructor
    KWB.BaseWizard.initializeBase(this);
    this._state = null;
}
//#endregion

// KWB.BaseWizard Class Body
//#region
KWB.BaseWizard.prototype = {
    // Sys.Component Overrides
    //#region
    dispose : function () {
        this._state = null;
        this._stateField = null;
        this.lastClicked = null;
        Sys.Application.remove_load(this.appLoad);
        KWB.BaseWizard.callBaseMethod(this, 'dispose');
    },
    
    initialize : function() {
        KWB.BaseWizard.callBaseMethod(this, 'initialize');
    },
    //#endregion
    
    // Properties
    //#region
    get_state : function() {
        return this._state;
    },
    
    set_state : function(value) {
        this._state = value;
    },
    //#endregion
    
    // Methods
    //#region
    getNext : function(current) {
        /// <summary>
        /// Override. Called when 'next' is called. Determines next frame to display
        /// </summary>
        /// <param name="current">current SelectedIndex</param>
        return current + 1;
    },
    
    getPrevious : function(current) {
        /// <summary>
        /// Override. Called when 'previous' is called. Determines previous frame to display
        /// </summary>
        /// <param name="current">current SelectedIndex</param>
        return current - 1;
    },
    
    finish : function() {
        /// <summary>
        /// Override. Called when 'finish' is pressed
        /// </summary>
        return true;
    },
    //#endregion
    
    // Event handlers
    //#region
    nextClick : function() {
        //TODO: add contents later
    }
    //#endregion
};
KWB.BaseWizard.registerClass('KWB.BaseWizard', Sys.Component);
//#endregion

// WizardStepType enumeration
//#region
KWB.WSType = function(){};
KWB.WSType.prototype = 
{
        Server  : 1, 
        Client  : 2,
        Service : 4 
}
KWB.WSType.registerEnum("KWB.WSType", false);
//#endregion

if(typeof(Sys) !== "undefined")Sys.Application.notifyScriptLoaded();

Now click Ctrl+M+E and you will see:

image

 

Veeery handy !

 

This is possible in VS because actually you can outline and collapse any selection you like manually, this work in most editors for VS.

If you are interested about that shortcuts see Text Manipulation Shortcut Keys, Visual C# Scheme . The shortcuts starting with Ctrl+M are the ones that manipulate test outlining and toggling.

 

Check out my previous post related to js files in AJAX scenario, and see how we can remove unnecessary white spaces and comment blocks from them for use in production:

Auto generate release scripts for web application using T4 template

 

Hope this helps.

Technorati Tags:

kick it on DotNetKicks.com

Sunday, April 13, 2008

Advanced Tooltip Control, ASP.NET AJAX

You can download source code only for control, sample page with control, or see the sample online.

I have created Tooltip control that replaces standard browsers' tooltips with custom ones. You can write your own html template to customize it's look and feel.

The control derives on the client from Sys.UI.Control, and is implemented as WebControl on the server.

It is really easy to use it and it gives really nice experience.

Here is a source for a very simple page showing how to use it:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Demo1.aspx.cs" Inherits="Demo1" %>

<%@ Register Assembly="Devarchive.Net" Namespace="Devarchive.Net" TagPrefix="dn" %>
<!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">
<head id="Head1" runat="server">
    <title>Demo1</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />
    <dn:HoverTooltip runat="server" ID="tooltip1" 
        OffsetX="10" 
        OffsetY="10" 
        DefaultTooltipText="This is default tooltip text"
        DefaultTooltipTitle="This is default title for the tooltip">
        <HtmlTemplate>
            <div style="border: solid 1px blue; background-color: Yellow; color: Blue; font-weight: bold">
                <b>{0}</b>
                <p>
                    {1}</p>
            </div>
        </HtmlTemplate>
    </dn:HoverTooltip>
    <div style="background-color:Yellow">
        <asp:Label runat="server" ID="lb1" Text="Hover me !" ToolTip="This is a tooltip for lb1" />
    </div>
    </form>
</body>
</html>

And code behind:

using System;

public partial class Demo1 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        tooltip1.AddTooltipControl(lb1);
    }
}

Resulting in:

image

The actual look and feel can be set inside "HtmTemplate" container, where we must place "{0}" and "{1}". "{0}" will be replaced by tooltip title and "{1}" will be replaced by the tooltip text for web control.

In this sample I assigned the following markup to HtmlTemplate property of the control:

<div style="border: solid 1px blue; background-color: Yellow; color: Blue; font-weight: bold">
    <b>{0}</b>
    <p>
        {1}</p>
</div>

Of course you can assign a better designed html yourself.

To attach a tooltip to some control on the page you can use "AddTooltipControl" public method of tooltip control. In the sample I use the code:

tooltip1.AddTooltipControl(lb1);

which attaches Label control to our sinned tooltip.

The tooltip finds that there is some text assigned to the "title" attribute of the span element generated by Label control and attaches mouseover and mouseout events to that control to show/hide corresponding tooltip.

"AddTooltipControl" method takes one argument of type "Control", it needs just the ClientID of the control to pass it to the script, the script is where searching for "title" attribute is made. This means you are not limited to attaching tooltips only to WebControls, any control can be specified, you have to ensure only that control is rendered with correct "title" attribute.

Tooltip control has also following four properties:

OffsetX, OffsetY - tooltip will appear with horizontal/vertical offset to the place where the cursor currently is,

DefaultTooltipText - this text will appear for controls that don't have title attribute set (Tooltip property on the server).

DefaultTooltipTitle - this text will appear for controls that don't have "tooltiptitle" attribute set. This is additional attribute that you can assign to the control. It will be rendered on the place where "{0}" placeholder will reside.

That's it about properties, now let's write some more attractive sample.

 

The nice thing about the control is - we can place tooltip design into Skin file. We can write there severat tooltip styles with different HtmlTemplate properties, and then use the skins to quickly style tooltips on different pages.

For the sample I created new Theme, and placed inside it my skin and css files that define the design of the tooltip controls:

image

Tooltip.skin file has the following contents:

<%@RegisterAssembly="Devarchive.Net"Namespace="Devarchive.Net"TagPrefix="dn"%>

<dn:HoverTooltip runat="server"SkinID="RounderCornersTooltip"
  
OffsetX="10"OffsetY="10"
  
DefaultTooltipText=""
  
DefaultTooltipTitle="Rounded Tooltip">
    <
HtmlTemplate>
            <
tablecellpadding="0"cellspacing="0"style="background-color:white;">
                <
tr>
                    <
tdvalign="top"class="corner">
                        <
asp:Imagerunat="server"ID="img1"Width="5px"Height="5px"
                          
ImageUrl="~/App_Themes/Default/Img/ctl.gif" />
                    </
td>
                    <
tdstyle="border-top: solid 1px #758611;"class="spacer">
                      
&nbsp;
                  
</td>
                    <
tdvalign="top"class="corner">
                         <
asp:Imagerunat="server"ID="img2"Width="5px"Height="5px"
                          
ImageUrl="~/App_Themes/Default/Img/ctr.gif" />
                    </
td>
                </
tr>
                <
tr>
                    <
tdstyle="border-left: solid 1px #758611"width="1">
                      
&nbsp;
                  
</td>
                    <
tdalign="left"valign="top"style="width:150px;">
                        <
b>{0}</b><br>
                        <
p>{1}</p>
                    </
td>
                    <
tdstyle="border-right: solid 1px #758611"width="1">
                      
&nbsp;
                  
</td>
                </
tr>
                <
tr>
                    <
tdvalign="bottom"class="corner">
                        <
asp:Imagerunat="server"ID="img3"Width="5px"Height="5px"
                          
ImageUrl="~/App_Themes/Default/Img/cbl.gif" />
                    </
td>
                    <
tdstyle="border-bottom: solid 1px #758611"class="spacer">
                      
&nbsp;
                  
</td>
                    <
tdvalign="bottom"class="corner">
                        <
asp:Imagerunat="server"ID="img4"Width="5px"Height="5px"
                          
ImageUrl="~/App_Themes/Default/Img/cbr.gif" />
                    </
td>
                </
tr>
            </
table>
    </
HtmlTemplate>
</
dn:HoverTooltip>

<
dn:HoverTooltip runat="server"SkinID="GoupBoxTooltip"
  
OffsetX="10"OffsetY="10"
  
DefaultTooltipText=""
  
DefaultTooltipTitle="Rounded Tooltip">
    <
HtmlTemplate>
        <
divclass="GoupBoxTooltip">
            <
tableborder="0"cellpadding="6"cellspacing="1"class="tborder"width="180">
                <
thead>
                    <
tr>
                        <
tdclass="thead">
                          
{0}
                        </td>
                    </
tr>
                </
thead>
                <
tbody id="collapseobj_forumrules">
                    <
tr>
                        <
tdclass="alt1">
                          
{1}
                        </td>
                    </
tr>
                </
tbody>
            </
table>
        </
div>
    </
HtmlTemplate>
</
dn:HoverTooltip>

 

Here I created two skins for my tooltips.

Now lets write the page markup for our second sample.

Demo2.aspx:

<%@ Page Theme="Default" Language="C#" AutoEventWireup="true" CodeFile="Demo2.aspx.cs" Inherits="Demo2" %>
<%@ Register Assembly="Devarchive.Net" Namespace="Devarchive.Net" TagPrefix="dn" %>
<!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">
<head id="Head1" runat="server">
    <title>Demo2</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server" />
    <dn:HoverTooltip runat="server" ID="tooltip1" SkinID="GoupBoxTooltip">
        </dn:HoverTooltip>
        <dn:HoverTooltip runat="server" ID="tooltip2" SkinID="RounderCornersTooltip">
        </dn:HoverTooltip>
            <br />
        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" 
            CellPadding="4" ForeColor="#333333" GridLines="None" 
            onrowdatabound="GridView1_RowDataBound">
            <FooterStyle BackColor="#990000" Font-Bold="True" ForeColor="White" />
            <RowStyle BackColor="#FFFBD6" ForeColor="#333333" />
            <Columns>
                <asp:BoundField DataField="FirstName" HeaderText="First Name" />
                <asp:BoundField DataField="LastName" HeaderText="Last Name" />
            </Columns>
            <PagerStyle BackColor="#FFCC66" ForeColor="#333333" HorizontalAlign="Center" />
            <SelectedRowStyle BackColor="#FFCC66" Font-Bold="True" ForeColor="Navy" />
            <HeaderStyle BackColor="#990000" Font-Bold="True" ForeColor="White" />
            <AlternatingRowStyle BackColor="White" />
        </asp:GridView>
        <br /><br />
            <asp:Button OnClientClick="return false" runat="server" Text="Submit" ID="Button1" 
                ToolTip="This button <span class='highlight'>Submits</span>" />
            <asp:Button OnClientClick="return false" runat="server" Text="Cancel" ID="Button2" 
                ToolTip="This button <span class='highlight'>Cancels</span>" />
            <asp:Button OnClientClick="return false" runat="server" Text="Delete" ID="Button3" 
                ToolTip="This button <span class='highlight'>Deletes</span>" />
            <asp:Button OnClientClick="return false" runat="server" Text="Insert" ID="Button4" 
                ToolTip="This button <span class='highlight'>Inserts</span>" />
            <asp:Button OnClientClick="return false" runat="server" Text="Edit" ID="Button5" 
                ToolTip="This button <span class='highlight'>Edits</span>" />
        <div>
        </div>
    </form>
</body>
</html>

 

Now as you see it is much simpler to created styled tooltip:

    <dn:HoverTooltip runat="server" ID="tooltip1" SkinID="GoupBoxTooltip">
    </dn:HoverTooltip>
    <dn:HoverTooltip runat="server" ID="tooltip2" SkinID="RounderCornersTooltip">
    </dn:HoverTooltip>

 

I have GridView on the page that lists some employees from Northwind database, - only their full names, I want to attach tooltips to each row, and when we hover the row I want to show some additional data about the Employee being hovered.

Also I have several buttons on the page and I want to show tooltips for them as well.

 

Here is the code behind file for the page:

using System;
using System.Data;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class Demo2 : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        tooltip2.AddTooltipControl(Button1);
        Button1.Attributes.Add("tooltiptitle", "Note:");
        tooltip2.AddTooltipControl(Button2);
        Button2.Attributes.Add("tooltiptitle", "Note:");
        tooltip2.AddTooltipControl(Button3);
        Button3.Attributes.Add("tooltiptitle", "Note:");
        tooltip2.AddTooltipControl(Button4);
        Button4.Attributes.Add("tooltiptitle", "Note:");
        tooltip2.AddTooltipControl(Button5);
        Button5.Attributes.Add("tooltiptitle", "Note:");

        string fileName = Server.MapPath("~/App_Data/TooltipSampleData.xml");

        DataTable dt = new DataTable();
        dt.ReadXml(fileName);
        GridView1.DataSource = dt;
        GridView1.DataBind();
    }
    protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            DataRowView drv = (DataRowView)e.Row.DataItem;
            if (drv != null)
            {
                e.Row.Style.Add(HtmlTextWriterStyle.Cursor, "pointer");
                //e.Row.ID = String.Format("row{0}", drv["EmployeeID"]);
                tooltip1.AddTooltipControl(e.Row);
                e.Row.Attributes["title"] = drv["Notes"].ToString();
                e.Row.Attributes["tooltiptitle"] = String.Format("Notes for {0} {1}:", drv["FirstName"], drv["LastName"]);
            }
        }
    }
}

For buttons I want to use different TooltipTitle for each. For this I am assigning them "tooltiptitle" attributes.

For each row of the GridView I use different text for the tooltip ("title" attribute) and different tooltip title ("tooltiptitle" attribute)

Here is the result:

image

as soon as another row is hovered - the corresponding tooltip is shown,

the same is for buttons:

image

Note that designs of tooltips are different - it depends on which tooltip I attach control to.

What is nice about the control - it changes it's position if hovered element is too close to the right or bottom bounds of the screen:

image

image

Happy programming !

Technorati Tags:

kick it on DotNetKicks.com

Thursday, April 10, 2008

Nice Visual Studio Skin

I never tried to use any skins with my Visual Studio dev environment before. After watching several screen casts from Rob Conery I wished to change my IDE a little and find theme similar to what I've seen in webcast.

After a little search I found download link on http://codeclimber.net.nz, post: Pimping my Visual Studio.

Yes, :) I like the theme :P, here is the screen shot of my working environment, it looks great also because of my new second 22 inch monitor :P

image

Wednesday, April 9, 2008

Client Side Timer Component, ASP.NET AJAX

You can download source code for control only, sample with control, and see the sample online.

We can use server side timer control that is available in AJAX library. But often we need timer that works on client only. Server Timer control posts back every time when tick event is raised. I need the similar control, that raises this event on client.

I found one very good sample, and to not waste time and reinvent already existing, I used this sample to create reusable Timer control.

The sample is from online AJAX docs website, Custom Demo.Timer Component

Here is a code of sample page demonstrating how to use the control:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="Devarchive.Net" Namespace="Devarchive.Net" TagPrefix="dn" %>
<%@ Register assembly="AjaxControlToolkit" namespace="AjaxControlToolkit" tagprefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
    <style type="text/css">
        #divOutput
        {
            border: dashed 1px black;
        }
        .highlight
        {
            background-color : Silver;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" />
        <dn:Timer runat="server" ID="Timer1" Interval="1000" ComponentID="t1" OnTick="onTick" />
        <script type="text/javascript">
            var i = 0;
            var b = false;
            function onTick() {
                $get("divOutput").innerHTML = new Date().format("HH:mm:ss tt");
                $get("divOutput").className = b ? "highlight" : "";
                b = !b;
            }
            function enableTimer() {
                $find("t1").set_enabled(true);
                updateStatus();
            }
            function disableTimer() {
                $find("t1").set_enabled(false);
                updateStatus();
            }
            function pageLoad(sender, args) {
                $find("slider").add_valueChanged(sliderChanged);
                updateStatus();
            }
            function sliderChanged(sender, args) {
                var interval = parseInt($find("slider").get_Value());
                $find("t1").set_interval(interval);
                updateStatus();
                
            }
            function updateStatus() {
                var interval = $find("t1").get_interval();
                var enabled = $find("t1").get_enabled();
                $get("divDetails").innerHTML = 
                    String.format(
                        "Interval: {0} ms<br />Enabled: {1}", 
                        interval,
                        enabled
                        );
                $get("btnDisabled").disabled = !enabled;
                $get("btnEnabled").disabled = enabled;
            }
        </script>
        <div id="divOutput">&nbsp;</div>
        <div id="divDetails">&nbsp;</div>
        <asp:TextBox runat="server" ID="tbInterval" Text="1000" />
        <cc1:SliderExtender BehaviorID="slider" ID="slider" Minimum="10" Maximum="10000" 
            TargetControlID="tbInterval" runat="server">
        </cc1:SliderExtender>
        <br />
        <input type="button" id="btnEnabled" value="Enable Timer" onclick="enableTimer();" />
        <input type="button" id="btnDisabled" value="Disable Timer" onclick="disableTimer();" />
    </form>
</body>
</html>

 

As you see, markup for the control on the page is:

<dn:Timer runat="server" ID="Timer1" Interval="1000" ComponentID="t1" OnTick="onTick" />

Interval - interval in milliseconds,

Enabled - set true to activate timer, or false to deactivate it,

ComponentID - assign some id to the control, using this id the component can be referenced from the script using syntax: $find("<ComponentID>), if not specified, ClientID is used

OnTick - specify client side function which will be called on every tick event. Alternatively you can attach event handlers from javascript using syntax:

$find(<ComponentID>).add_tick(myHandler), and detach them using syntax: $find(<ComponentID>).remove_tick(myHandler).

 

The running sample page looks like this:

image

 

Hope this helps.

Technorati Tags:

kick it on DotNetKicks.com

Monday, April 7, 2008

White Spaces in HTML Source - Be Careful

Hmm, Interesting. I am going to show you a very interesting figures about white spaces in HTML source of our ASP.NET pages.

I was a little puzzled when I saw this figures and understood that there is no optimization or something in IIS that removes or somehow optimizes this thing.

Suppose we have a page with a following markup:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<% for (int i = 0; i < 20000; i++)
{
%>test <%
} 
%>
</div>
</form>
</body>
</html>

The page simply writes word "test" 20 000 times to the output of the page. Note that in this code I did not use indents and formatting for every new nested element as we normally do.

Then I saved the source of the page to the disk and opened "properties" for the txt file, size was 98,1 kb.

Now let's rewrite the page in the manner we normally write, with indents etc. :

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <% for (int i = 0; i < 20000; i++)
           {
        %>
                                        test
        <%
            } 
        %>
    </div>
    </form>
</body>
</html>

I saved the source and the size iiis .. 1.06 MB !!!

Amazing - just indenting word "test" using several tab symbols gives such a huge difference !

Size of new page is 10 times larger then previous one.

 

Lets see if it actually applies to network traffic. I will use firebug add-in of Mozilla Firefox browser to measure this:

Here is request for first, "optimized" page:

image

And that is the network request for second page:

image

Response time is 5 times faster in first case as well. And this is on my local machine! What if I have to load the page over the internet?

 

I was interested with problem enough to start watching to source code of popular sites. And .. they've got a secret ! It seems they know about this very well.

Just try it yourself - go to the microsoft.com and see the source of the page :) Then go to the google.com and do the the same.

Why this is not well documented fact?

Did you hear about it before?

 

Technorati Tags:
kick it on DotNetKicks.com

Sunday, April 6, 2008

Using IScriptControl interface with UserControls in ASP.NET AJAX Applications.

Often we need to write some custom AJAX component that will serve UserControl, and will define some client side functionality that is specific only to that UserControl.

OK we can find a samples in the AJAX docs covering how to create custom inheritor from Sys.Component or Sys.UI.Control or Sys.UI.Behavior classes in javascript part of AJAX framework.

We can also find how to create custom component, extender or control on server side. But this samples cover only Custom Controls. I not use Custom Control very often during every day development. Custom Controls are supposed to be reusable and for some very specific part of the page creating a new Custom Control is not an option - we use User Controls.

I tried to use the same approach as described in the links above with User Controls, and it works fine.

I will take one of the samples from AJAX docs and show how to use it with User Controls instead.

To implement IScriptControl interface we need to make the following steps:

1) Write client side script describing how control behaves itself on the client (I am taking them for demonstration from AJAX docs website)  - this is Timer class you can find on the link.

2)Write PreRender event handler for UserControl:
protected override void OnPreRender(EventArgs e)
{
    ScriptManager.GetCurrent(Page).RegisterScriptControl(this);
    base.OnPreRender(e);
}

3) Write Render event handler for UserControl:

protected override void Render(HtmlTextWriter writer)
{
    base.Render(writer);
    ScriptManager.GetCurrent(Page).RegisterScriptDescriptors(this);
}

4) Implement IScriptControl interface for the user control:

#region IScriptControl Members

    public System.Collections.Generic.IEnumerable<ScriptDescriptor> GetScriptDescriptors()
    {
        ScriptComponentDescriptor scd =
            new ScriptControlDescriptor("Demo.Timer", ClientID);
        scd.AddProperty("enabled", true);
        scd.AddProperty("interval", 2000);
        scd.AddEvent("tick", "OnTick");
        yield return scd;
    }

    public System.Collections.Generic.IEnumerable<ScriptReference> GetScriptReferences()
    {
        ScriptReference sr = new ScriptReference("~/JScript.js");
        yield return sr;
    }

#endregion

 

That's it ! Now drag several UserControls on the page and see result:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register src="WebUserControl.ascx" tagname="WebUserControl" tagprefix="uc1" %>
<html>
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" />
        <div>
            <uc1:WebUserControl ID="WebUserControl1" runat="server" />
            <uc1:WebUserControl ID="WebUserControl2" runat="server" />
            <uc1:WebUserControl ID="WebUserControl3" runat="server" />
            <uc1:WebUserControl ID="WebUserControl4" runat="server" />
            <uc1:WebUserControl ID="WebUserControl5" runat="server" />
        </div>
    </form>
</body>
</html>

 

image

The Timer control is working, and no difficulties with creating scripts in page markup.

Let's see what source was generated for our page, first of all we can find script was linked to the page:

<script src="JScript.js" type="text/javascript"></script>

And the $create function was called for each user control:

Sys.Application.initialize();
Sys.Application.add_init(function() {
    $create(Demo.Timer, {"enabled":true,"interval":2000}, {"tick":function(sender, args) {var result = $get('WebUserControl1_divOutput'); result.innerText = parseInt(result.innerText) + 1; }}, null, $get("WebUserControl1"));
});

 

As you see with very little efforts and in elegant way we use built in AJAX IScriptControl interface to do all the work for creating and initializing Sys.Component inheritor that is implementing some non visual functionality - timer in this case.

 

Here is the full source code for code-behind file of our UserControl again:

 

using System;
using System.Web.UI;

public partial class WebUserControl : System.Web.UI.UserControl, IScriptControl
{
    protected void Page_Load(object sender, EventArgs e)
    {

    }

    protected override void OnPreRender(EventArgs e)
    {
        ScriptManager.GetCurrent(Page).RegisterScriptControl(this);
        base.OnPreRender(e);
    }

    protected override void Render(HtmlTextWriter writer)
    {
        base.Render(writer);
        ScriptManager.GetCurrent(Page).RegisterScriptDescriptors(this);
    }

    #region IScriptControl Members

    public System.Collections.Generic.IEnumerable<ScriptDescriptor> GetScriptDescriptors()
    {
        ScriptComponentDescriptor scd =
            new ScriptControlDescriptor("Demo.Timer", ClientID);
        scd.AddProperty("enabled", true);
        scd.AddProperty("interval", 2000);

        scd.AddEvent("tick",
            "function(sender, args) {var result = $get('" + 
            divOutput.ClientID + 
            "'); result.innerText = parseInt(result.innerText) + 1; }");

        yield return scd;
    }

    public System.Collections.Generic.IEnumerable<ScriptReference> GetScriptReferences()
    {
        ScriptReference sr = new ScriptReference("~/JScript.js");
        yield return sr;
    }

    #endregion
}

Hope this helps.

Technorati Tags:

Thursday, April 3, 2008

StateBag Control - Simplifying Data Exchange in Heavy AJAX Pages - Sample Source Code Part 2

You can download code for this article or see it live.

Also you can download source code for StateBag control only.

This is second post with samples in a series of articles about exchanging complex objects between different parts of AJAX enabled page.

You can see the whole article by following these two links:

StateBag Control - Simplifying Data Exchange in Heavy AJAX Pages

StateBag Control - Simplifying Data Exchange in Heavy AJAX Pages - Sample Source Code Part 1

I will remind that I am going to create a sample to demonstrate a power of JSON serialization in AJAX framework, and demonstrate a usage of StateBag control I created in Part 1

Here is the exact picture of what I am trying to do in this post:

image_thumb1

The exact explanation with sample code for "Dynamic Area No 1" can be viewed in the second part, where we created sample using dynamic creation of google charts data in the browser, and then submitting the data to the server.

Today I want to create two remaining areas

Let's start by creating state object:

namespace Devarchive.Net
{
    public class Sample2State
    {
        public string IFrameIO;
        public string ServiceIO;
        public string UpdatePanelIO;
    }
}

Page markup looks like this:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="Devarchive.Net" Namespace="Devarchive.Net" TagPrefix="dn" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>StateBag - Sample 2</title>
    <style type="text/css">
        .div
        {
            border:dashed 1px Black;
            float:left; 
            width:200px;
            height:500px;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="SM" runat="server">
            <Services>
                <asp:ServiceReference Path="~/WebService.asmx" />
            </Services>
        </asp:ScriptManager>
        

<dn:StateBag ID="StateBag1" runat="server" StateID="MyState1" />

        <script type="text/javascript">
            var stateBag = null;
            function pageLoad() {
                stateBag = $find("MyState1");
                if(stateBag) {
                    stateBag.add_propertyChanged(stateBagProperyChanged);
                }
            }
            function stateBagProperyChanged(sender, args) {
                if(stateBag && args.get_propertyName()=="state") {
                    outPutCurrentState("property changed in StateBag:");
                }
            }
            function echo() {
                if(stateBag) {
                    // process data from statebag - including parts shared with web service 
                    // and iframe            
                    $get("divOutput").innerHTML = stateBag.get_state().UpdatePanelIO + " - OK.";
                }
            }
            function modify() {
                if(stateBag) {
                    var state = stateBag.get_state();
                    // process data from statebag - including parts shared with web service 
                    // and iframe
                    state.UpdatePanelIO = "Modified from <b>Main Page</b>, time:" + new Date();
                    stateBag.set_state(state);
                    stateBag.raisePropertyChanged('state');
                }
            }
            function outPutCurrentState(additionalMessage) {
                var sb = new Sys.StringBuilder();
                sb.appendLine(additionalMessage+"<br>");
                var state = stateBag.get_state();
                for(var key in state) {
                    sb.appendLine(String.format("{0}: {1}<br>", key, state[key]));
                }
                $get("divOutput").innerHTML = sb.toString();
            }
            
            // service related functions
            
            function echoService() {
                if(stateBag) {
                    WebService.Echo(stateBag.get_state(), echoCallback, errorCallback, null);
                }
            }
            function modifyService() {
                if(stateBag) {
                    WebService.Modify(stateBag.get_state(), modifyCallback, errorCallback, null);
                }
            }
            function echoCallback(result) {
                if(result) {
                    $get("divService").innerHTML = result;
                }
            }
            function modifyCallback(result) {
                if(result) {
                    var state = result;
                    stateBag.set_state(state);
                    stateBag.raisePropertyChanged('state');
                }
            }
            function errorCallback(result) {
                // error handling here
            }
        </script>
        <div>
            <div class="div" runat="server">
                <h4>Echo/Modify state from MainPage</h4>
                <input type="button" id="btnEcho" value="Echo" onclick="echo()" />
                <input type="button" id="btnModify" value="Modify" onclick="modify()" />
                <div  id="divOutput">
                </div>
            </div>
            <div class="div">
                <h4>Echo/Modify state from WebService</h4>
                <input type="button" id="btnEchoService" value="Echo" onclick="echoService()" />
                <input type="button" id="btnModifyService" value="Modify" onclick="modifyService()" />
                <div id="divService">
                </div>
            </div>
            <div class="div">
                <h4>Echo/Modify state from IFrame</h4>
                <iframe scrolling="no" style="border:none; width:190px; height:490px" src="IFrameContents.aspx"></iframe>
            </div>
        </div>
    </form>
</body>
</html>

As you see I created three areas in the page - three divs.

One is modified from javascript, second from iframe, and third using webservice.

You should know that you can simply postback the main form and you will have entire latest state also on the c# code in the code behind of the main page - also if you create one more stateBag control inside iframe and postback the iframe you will get the latest state object also in the code-behind of the iframe. This way we can sync the state even across many pages

 

Now the code of the iframe:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="IFrameContents.aspx.cs" Inherits="IFrameContents" %>

<!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">
<head runat="server">
    <title>Inner contents of an iframe</title>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="SM" />
    <script type="text/javascript">
        var stateBag = null;
        function pageLoad() {
            if(window.parent != null) {
                stateBag = window.parent.$find("MyState1");
                if(stateBag) {
                    stateBag.add_propertyChanged(stateBagProperyChanged);
                }
            }
        }
        function stateBagProperyChanged(sender, args) {
            if(stateBag && args.get_propertyName()=="state") {
                outPutCurrentState("property changed in StateBag:");
            }
        }
        function echo() {
            if(stateBag) {
                // process data from statebag - including parts shared with web service 
                // and main page            
                $get("divOutput").innerHTML = stateBag.get_state().IFrameIO + " - OK.";
            }
        }
        function modify() {
            if(stateBag) {
                var state = stateBag.get_state();
                // process data from statebag - including parts shared with web service 
                // and main page
                state.IFrameIO = "Modified from <b>Iframe</b>, time:" + new Date();
                stateBag.set_state(state);
                stateBag.raisePropertyChanged('state');
            }
        }
        function outPutCurrentState(additionalMessage) {
            var sb = new Sys.StringBuilder();
            sb.appendLine(additionalMessage+"<br>");
            var state = stateBag.get_state();
            for(var key in state) {
                sb.appendLine(String.format("{0}: {1}<br>", key, state[key]));
            }
            $get("divOutput").innerHTML = sb.toString();
        }
    </script>
    <input type="button" id="btnEcho" value="Echo" onclick="echo()" />
    <input type="button" id="btnModify" value="Modify" onclick="modify()" />
    <div id="divOutput"></div>
    </form>
</body>
</html>

Note how we reference the object in the parent window, and even subscribe to it's events. This way we can be in sync with centralized stateBag placed on the main page.

 

And the last important code - how we modify and use state object in the web service:

using System;
using System.Collections;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Script.Services;
using Devarchive.Net;

[WebService]
[ScriptService]
public class WebService : System.Web.Services.WebService
{
    [WebMethod]
    public string Echo(Sample2State state)
    {
        // process data from statebag - including parts shared with iframe and main page
        return state.ServiceIO + " - OK.";
    }
    [WebMethod]
    public Sample2State Modify(Sample2State state)
    {
        state.ServiceIO = "modified from <b>web service</b>, time:" + DateTime.Now.ToString();
        // process data from statebag - including parts shared with iframe and main page
        return state;
    }
}

The application will look like this:

image

Yes I know it looks awful :D - I am programmer, not designer :) Hope you understand the idea.

Now let's redraw the diagram I placed in the beginning of the article, and mark the places where the state can be used/modified with red mark:

image

As you see we can access or modify it using javascript from any area in the browser (left side), and also can send/receive and use it in code behinds of iframe pages, main page, and code for web service.

Hope this helps.

Technorati Tags: