Groovy bitsbringing new skills to your web development team

The White Paper

Contents

Summary

These are notes on how this web site is implemented. The intended audience is web designers and developers. Groovy bits places this information in the public domain. Feel free to clone and modify the markup and code you see here.

Introduction

Groovybits.com blends traditional HTML with cutting edge technologies like Atlas (AJAX) and CSS Friendly control adapters.

These are not flavor-of-the-month technologies, selected because they are the "in" thing at the moment. We use them because they work and they make our job substantially easier.

For example, Groovybits.com occasionally draws attention to particularly important content by enclosing it within a rectangle whose corners are rounded. This subtle detail softens the presentation, making it more elegant, less garish. It's been underused on the web simply because its usual implementation requires the addition of fairly complex markup and custom-built images. Atlas gets the job done with a couple simple tags.

The portfolio page uses Atlas in a far more sophisticated way: to replace postbacks with AJAX client/server communications.

The portfolio uses a slideshow to walk the visitor through pictures and descriptions of our past projects. The images and text change without re-rendering the nearby menus and other page decorations. There are no full-page refreshes or postbacks.

This slideshow is implemented with an <atlas:UpdatePanel> tag. It's purpose on your ASPX web page is to replace the usual client-side JavaScript for handling postbacks with a far less disruptive mechanism that uses AJAX to send messages between the client browser and the web server. This may sound complicated but its actually really simple to use. You just wrap your existing markup in an <atlas:UpdatePanel> tag and, poof, wherever your old markup used to cause postbacks, now uses a much more sophisticated page update system.

CSS Friendly control adapters are used in this site so the design can be better encapsulated and controlled with Cascading Style Sheets. They also make the pages more compliant to W3C and 508 (accessibility) standards. Of course, since Microsoft commissioned Groovy bits to build these adapters it's rather natural that we use them, too.

Goals

From time to time it's helpful to remind ourselves of the big picture. It's easy to agree that we want web sites that:

  • Present information concisely and in a compelling way.
  • Run everywhere: all modern browsers and operating systems.
  • Are accessible and standards compliant.
  • Offer private and secure access to confidential information.
  • Can be deployed quickly and inexpensively.
  • Are easy to implement, change and maintain.

Hosting

In the past you typically paid a hefty premium to run ASP.NET pages. That's changed. Today there are plenty of inexpensive hosting companies with web servers that support ASP.NET 2.0, many including SQL Server databases.

Groovybits.com uses GoDaddy to host its pages. We consider the hosting market to be fluid, though, with new companies emerging as the leaders in price and performance.

Technologies

There are lots of technologies that help authors describe the appearance and behavior of web pages:

  • ASP.NET
  • PHP
  • ColdFusion (CFML)
  • J2EE (JSP)
  • Perl (CGI)

Web shops tend to specialize in one of these languages. Groovy bits uses ASP.NET, though we have used most of the alternatives at one point or another in the past.

Today, Groovybits.com is deployed on web servers that use Microsoft web technologies:

ASP.NET 2.0

ASP.NET 2.0 is a language created by Microsoft to describe the appearance and behavior of web pages. It goes beyond static HTML to help a web site like Groovybits.com dynamically connect to databases, email systems, etc. For more information about ASP.NET please visit http://www.asp.net.

Membership

Login form
Groovy bits privately shares schedules, mock-ups, test pages, etc. with its clients. These things aren't intended for the general public so they are secured with a login system integrated with the Membership services built into ASP.NET.

The login form that requests the visitor's credentials is implemented using the <asp:Login> tag:

ASP.NET
1
<asp:Login ID="Login1" runat="server" OnLoggedIn="OnLoggedIn" DisplayRememberMe="true" TitleText="Please log in" CssSelectorClass="PrettyLogin" />

Under the hood, the <asp:Login> tag uses the ASP.NET Membership services to store names, passwords, roles, etc. in a database. Microsoft provides utilities to create a local instance of this database. Hosting companies like GoDaddy provide equivalent tools online to set up the Membership schema for remote databases.

The web.config file tells the web site how to connect to the Membership database:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
  <connectionStrings>
    <remove name="LocalSqlServer"></remove>
    <add name="LocalSqlServer" connectionString="Data Source=somecomputerwithsqlserver.someserver.com;Initial Catalog=somedbname;User ID=someone;Password=something;" providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <system.web>
    <roleManager enabled="true"/>
    <authentication mode="Forms">
      <forms loginUrl="/clientDocs/login.aspx" protection="All" timeout="30" name="AppNameCookie" path="/" requireSSL="false" slidingExpiration="true" defaultUrl="default.aspx" cookieless="UseCookies" enableCrossAppRedirects="true"/>
    </authentication>
    <machineKey validationKey="somelongstring" validation="SHA1"/>
    <membership>
      <providers>
        <remove name="AspNetSqlMembershipProvider"/>
        <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="LocalSqlServer" />
      </providers>
    </membership>
  </system.web>
</configuration>

The web.config shown above is representative. The actual web.config used by Groovybits.com sets these additional attributes for the AspNetSqlMembershipProvider:

  • minRequiredPasswordLength
  • minRequiredNonalphanumericCharacters
  • passwordAttemptWindow
  • passwordStrengthRegularExpression
  • enablePasswordRetrieval
  • enablePasswordReset
  • requiresQuestionAndAnswer
  • applicationName
  • requiresUniqueEmail
  • passwordFormat
  • maxInvalidPasswordAttempts

The OnLoggedIn attribute in the <asp:Login> tag identifies the server-side method you want called when valid credentials are provided. Take a look at the logic below, used by Groovybits.com's login page. After the user provides valid credentials they are allowed to proceed on to whatever protected page they requested. Or, if they navigated directly to the login page they are redirected to a general-purpose page for their "role." ASP.NET lets each user be assign to roles that help define what that user is allowed to do on the web site.

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
protected void OnLoggedIn(object sender, EventArgs e)
{
    //  We should be able to get the roles by calling Roles.GetRolesForUser() but that
    //  won't work at this point.  The forms auth ticket isn't quite completely set up so
    //  a parameterless call to GetRolesForUser won't work because the current user isn't
    //  really known by the system.  We can work around this by explicitly saying that we
    //  want the roles for the user that just logged in.
    //
    //  See the reply sent at 06-06-2005, 2:07 PM in this thread:
    //  http://forums.asp.net/1057611/ShowPost.aspx
    //
    if ((Request.QueryString["returnUrl"] != null) && (Request.QueryString["returnUrl"].Length > 0))
    {
        Response.Redirect(Request.QueryString["returnUrl"]);
    }
    else
    {
        String[] roles = Roles.GetRolesForUser(Login1.UserName);
        if ((roles != null) && (roles.Length > 0))
        {
            String defaultPage = ResolveUrl("~/clientdocs/" + roles[0] + "/index.aspx");
            String fullPath = Server.MapPath(defaultPage);
            if ((fullPath != null) && (System.IO.File.Exists(fullPath)))
            {
                Response.Redirect(defaultPage);
            }
        }
    }

    //  By default the Login control will redirect us to the default page in the site.
}

Groovybits.com uses a CSS Friendly adapter for the Login control. This lets us avoid having to use difficult-to-maintain LayoutTemplate markup in the <asp:Login> tag but still render great HTML and CSS.

Multiple site maps

Groovybits.com uses <asp:TreeView> tags to display hierarchical data like the table of contents at the top of this page or the one in the source code viewer.

You can invent your own XML schema for the data you want shown in your <asp:TreeView> but then you have to use an explicit XPath or <asp:TreeNodeBinding> to retrieve those data. Instead, you can use a <asp:SiteMapDataSource> which is like the <asp:XmlDataSource> tag but uses a preset schema like this:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
  <siteMapNode>
    <siteMapNode url="~/page1.aspx" title="foo" >
      <siteMapNode url="~/page2.aspx" title="abc" />
      <siteMapNode url="~/page2.aspx" title="xyz" />
    </siteMapNode>
    <siteMapNode url="~/page1.aspx" title="bar" >
      <siteMapNode url="~/page2.aspx" title="hello" />
      <siteMapNode url="~/page2.aspx" title="world" />
    </siteMapNode>
  </siteMapNode>
</siteMap>

By default <asp:SiteMapDataSource> tag expects to find its data in a file called Web.sitemap. It's easy to use the optional SiteMapProvider attribute in the <asp:SiteMapDataSource> tag to refer to other site map "providers" you configure in your web.config:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
  <system.web>

    [removed for brevity]

    <siteMap>
      <providers>
        <add siteMapFile="Footer.sitemap" name="FooterSiteMapProvider" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
        <add siteMapFile="~/LeftOverBits/siteWhitePaperToc.sitemap" name="SiteWhitePaperTocSiteMapProvider" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
      </providers>
    </siteMap>

  </system.web>
</configuration>

Here's the markup that uses one of these alternate site map providers:

ASP.NET
1
2
3
<asp:TreeView ID="SiteWhitePaperTocTree" runat="server" DataSourceID="SiteWhitePaperTocDS" CssSelectorClass="SiteWhitePaperToc" ExpandDepth="0" />
<asp:SiteMapDataSource runat="server" ID="SiteWhitePaperTocDS" SiteMapProvider="SiteWhitePaperTocSiteMapProvider" ShowStartingNode="false" />

By using multiple site maps instead of inventing your own XML schema you end up with incredibly simple markup for the <asp:TreeView> and its data source.

XmlDataSource

The portfolio viewer in Groovybits.com is a good example of displaying individual records of data. Site maps, discussed earlier, are also sets of records but their hierarchy is critical. By contrast, the portfolio data consists of a simple list of records. Furthermore, a site map's fundamental purpose is to map some text to a URL. By contrast, the portfolio data describes thumbnail images and several different portions of text. It doesn't make sense to force these data to fit into the schema of a site map. Instead, we can invent our own, simple XML schema for this list of portfolio records.

XML
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8" ?>
<Portfolio>
  <Project Name="DrDanson.com" Text="We provided the design and development services for this site." ImageUrl="~/Portfolio/MySites_DrDanson.jpg">
    <Highlight>eCommerce site</Highlight>
    <Highlight>content-management system</Highlight>
    <Highlight>integrated database management</Highlight>
  </Project>
  [removed for brevity]
</Portfolio>

A slide is merely a <asp:FormView> tag bound to one of these Project records:

ASP.NET
1
2
3
4
5
6
7
8
9
10
<asp:FormView ID="SlideShow" Runat="server" DataSourceID="PortfolioDS" HeaderText="Some of our clients..." AllowPaging="True" CssSelectorClass="SlideShow">
  <ItemTemplate>
    <div class="ProjectThumbnail">
        <asp:Image runat="server" ImageUrl='<%# XPath("./@ImageUrl") %>' />
    </div>
    [removed for brevity]
  </ItemTemplate>
</asp:FormView>
<asp:XmlDataSource ID="PortfolioDS" DataFile="~/App_Data/Portfolio.xml" runat="server" XPath=".//Portfolio/Project" />

The XPath value ".//Portfolio/Project" selects the set of all <Project> tags that are descendants of the <Portfolio> tag.

The <asp:FormView> tag's <ItemTemplate> includes an <asp:Image> tag ImageUrl attribute is set equal to the ImageUrl field by using the XPath expression "./@ImageUrl".

CSS Friendly control adapters

At the beginning of 2006, Microsoft asked Groovy bits to help develop a way to adapt ASP.NET to generate HTML markup that was particularly easy to style with CSS. The CSS Friendly ASP.NET 2.0 Control Adapters kit was first published in May, 2006. Groovy bits is currently under contract with Microsoft to build a second beta and then a production-grade version of the kit, both of which are expected to be released in the latter half of 2006.

To understand why control adapters are so important we can take a more careful look at this site's menus and trees.

Groovy bits prefers to represent menus and trees with nest <ul> tags rather than <table> tags. This increases the accessibility of these pages to screen readers, etc. It also helps us build pages that pass XHTML validation more easily.

We could compose all of these HTML unordered lists manually but that would be a nightmare to maintain over time as we add or remove items from the menus and trees in this site.

It's far easier for us to write a very compact <asp:Menu> or <asp:TreeView> tag that gets its items from external database or XML file. That way, we can change the contents of the menu or tree by changing that external data without risking touching the page's menu and tree markup and code. The great thing about using adapters is that we can use these classic ASP.NET controls (<asp:Menu> or <asp:TreeView>) without compromising on the page's ultimate accessibility or standards compliance.

This site's main navigation menu can be created with just a couple lines of markup:

ASP.NET
1
2
3
<asp:Menu ID="MainMenu" runat="server" DataSourceID="MainSiteMapDS" Orientation="Horizontal" CssSelectorClass="MainMenu" />
<asp:SiteMapDataSource runat="server" ID="MainSiteMapDS" ShowStartingNode="false" />

In this case, the <asp:SiteMapDataSource> doesn't specify a particular SiteMapProvider the site map data itself must be stored in the Web.sitemap file in the root of the site. It's a simple XML file that looks like this:

XML
1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
  <siteMapNode>
    <siteMapNode url="/default.aspx" title="home" description="home" />
    <siteMapNode url="/about.aspx" title="about" description="about us" />
    <siteMapNode url="/portfolio.aspx" title="portfolio" description="portfolio" />
    <siteMapNode url="/leftoverbits/flickerfix.aspx" title="extra bits" description="extra bits" />
    <siteMapNode url="/clientDocs/login.aspx" title="login" description="client login" />
    <siteMapNode url="/contact.aspx" title="contact" description="contact us" />
 </siteMapNode>
</siteMap>

Atlas

Atlas is Microsoft's response to the current trend emphasizing AJAX technology to build richer web experiences that work across a wide range of modern browsers and operating systems.

Early adopters of Atlas may find the implementations described here helpful. It is shockingly simple to create rounded rectangles, textbox watermarks, postback-less form submissions and other common web behaviors with Atlas. Hopefully the examples below convey that message in through thier concision.

RoundedCornersExtender

Rounded rectangles, when used judiciously in a web site's design, can soften a design and add a subtle sophistication. They are often avoided, though, because their traditional implementation has been too expensive: custom-built image files (GIF or JPG) laid out using a table or a complex nesting of <div> tags.

Atlas makes all that pain a thing of the past. As the following code shows, it takes just a couple of extra tags now to convert a square <asp:Panel> into a rounded rectangle. It may sound a bit silly, but this little feature is one of my favorites in the whole Atlas framework.

ASP.NET
1
2
3
4
5
6
7
<asp:Panel id="quickContact" runat="server" CssClass="quickContact">
  [removed for brevity]
</asp:Panel>
<atlasControlToolkit:RoundedCornersExtender ID="rce1" runat="server">
  <atlasControlToolkit:RoundedCornersProperties TargetControlID="quickContact" Radius="15" Color="#FFFFFF" />
</atlasControlToolkit:RoundedCornersExtender>

HoverMenuExtender

Great web pages present a concise message that is easy to understand at a glance... but simultaneously offer a quick and easy way to get more details.

Groovy bits wants its web site's home page to convey its main message within a couple of seconds. We want visitors to be able to judge at a glance if Groovy bits offers services that they need. So we knew that we had to keep the home page's content very sparse, very focused.

Visitors whose needs match the home page's central message will want more detail, though. And they aren't going to want to hunt around in the web site to find it. Our solution was to design a home page that layers detailed information on top of a concise message. Visitors merely need to pass their cursor over parts of the concise message to see relevant details.

Atlas' handy HoverMenuExtender makes it easy to implement this sort of scheme.

ASP.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<asp:Panel id="whatMaster" CssClass="master" runat="server">
  <h2>What we do</h2>
  <h3>Create / Improve / Evaluate<br /> Websites</h3>
</asp:Panel>
<asp:Panel id="whatDetail" CssClass="detail" runat="server">
  <ul>
    <li>Build, configure and deploy web sites. Turn comps into HTML &amp; CSS. Add databases and services to static sites.</li>
    <li>Make existing web sites better by injecting new, faster, more maintainable technology.</li>
    <li>Trim downstream implementation and maintenance costs by helping you make good technology choices today.</li>
  </ul>
</asp:Panel>
<atlasControlToolkit:HoverMenuExtender ID="hme1" runat="Server">
  <atlasControlToolkit:HoverMenuProperties TargetControlID="whatMaster" HoverCssClass="masterHovered"
      PopupControlID="whatDetail" PopupPosition="Right" OffsetX="-10" OffsetY="0" PopDelay="50" />
</atlasControlToolkit:HoverMenuExtender>

CollapsiblePanelExtender

At the bottom of our web pages we offer a way to see our pages are constructed. Look in the footer of each page for the text that says: See how this web site is built...

This is a way for Groovy bits to contribute to the development community in the spirit of open source. The source code viewer we offer is composed of two parts:

  • A table-of-contents that lets you browser the files you may view.
  • A panel where the source code for one of these files is displayed.

When you are looking at a file's source code you want to use as much of the page's height/width as possible. At that moment you aren't using the table-of-contents (the tree) at all so certainly don't want it to get in the way of viewing the source code. In fact, you only want that tree to appear when you are ready to navigate to examine the markup/code in another file.

Our solution is to hide (collapse) the panel where the table-of-contents (tree) lives until you explicitly indicate that you want it shown. Atlas makes this simple to implement with its CollapsiblePanelExtender.

ASP.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<asp:Panel runat="server" id="ShowHideTree" CssClass="ShowHideTree">
  [removed for brevity]
</asp:Panel>
<asp:Panel runat="server" id="SCVTree" CssClass="SCVTree">
  [removed for brevity]
</asp:Panel>
<div id="SCVPanel">
  [removed for brevity]
</div>
<AtlasControlToolkit:CollapsiblePanelExtender ID="cpe1" runat="Server">
  <AtlasControlToolkit:CollapsiblePanelProperties
     TargetControlID="SCVTree"
     ExpandControlID="ShowHideTree"
     CollapseControlID="ShowHideTree"
     Collapsed="True"
     CollapsedSize="0"
     ExpandDirection="Vertical"
     TextLabelID="ShowHideTreeLabel"
     ExpandedText="Hide"
     CollapsedText="Show"
     ImageControlID="ShowHideTreeImage"
     ExpandedImage="~/media/collapse_blue.jpg"
     CollapsedImage="~/media/expand_blue.jpg" />
</AtlasControlToolkit:CollapsiblePanelExtender>

TextBoxWatermarkExtender

Traditional web forms place a textual label next to each form element to describe its purpose. Lately, though, authors have begun to recognize that putting those textual hints inside the form elements, instead, conserves precious page real estate. This is sometimes called using a textbox watermark.

Until Atlas came along we didn't implement watermarking much simply because it wasn't cost effective for the client. We needed to bake in too much markup and JavaScript code at an expense to the client that couldn't be justified by the improvement in the end user experience. That's a shame because watermarked forms really are better. So it's great that Atlas now makes the implementation so easy. Take a look at just how concisely this is done in this site:

ASP.NET
1
2
3
4
5
6
7
8
9
10
11
<asp:TextBox ID="YourName" runat="server" />
<asp:TextBox ID="EmailAddr" runat="server" />
<asp:TextBox ID="PhoneNumber" runat="server" />
<asp:TextBox ID="Message" Rows="8" TextMode="MultiLine" runat="server" />
<atlasControlToolkit:TextBoxWatermarkExtender id="tbwe1" runat="server">
  <atlasControlToolkit:TextBoxWatermarkProperties TargetControlID="YourName" WatermarkText="your name" WatermarkCssClass="watermarked" />
  <atlasControlToolkit:TextBoxWatermarkProperties TargetControlID="EmailAddr" WatermarkText="your email address" WatermarkCssClass="watermarked" />
  <atlasControlToolkit:TextBoxWatermarkProperties TargetControlID="PhoneNumber" WatermarkText="your telephone number" WatermarkCssClass="watermarked" />
  <atlasControlToolkit:TextBoxWatermarkProperties TargetControlID="Message" WatermarkText="Do you prefer that we write or call you?" WatermarkCssClass="watermarked" />
</atlasControlToolkit:TextBoxWatermarkExtender>

UpdatePanel

In the section above we looked at how Atlas lets us add watermarks. The same eMail form uses Atlas in other ways, too. For example, when the Send button is clicked we use Atlas to submit the request without forcing a full page refresh. The result is a far smoother experience for the end user. The page doesn't "flash" because it isn't forced to fully rebuilt itself. Atlas helps us limit the impact of the form submission so only a tiny portion of the HTML DOM must be repainted.

We wrap the form elements in an <atlas:UpdatePanel>:

ASP.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<asp:Panel id="quickContact" runat="server" CssClass="quickContact">
  <atlas:UpdatePanel runat="server" ID="up1">
    <ContentTemplate>
      <fieldset>
        <legend>Quick eMail</legend>

        <asp:RequiredFieldValidator EnableClientScript="false" Display="Static" Text="*" ID="reqValidatorYourName" ControlToValidate="YourName" runat="server" CssClass="errorflag" />
        <asp:TextBox ID="YourName" runat="server" TabIndex="1" />
                
        <asp:RequiredFieldValidator EnableClientScript="false" runat="server" ControlToValidate="EmailAddr" ID="reqValidatorEmailAddr" Display="Static" Text="*" CssClass="errorflag" /> 
        <asp:TextBox ID="EmailAddr" runat="server" TabIndex="3" />
                                
        <asp:RequiredFieldValidator Enabled="false" EnableClientScript="false" runat="server" ControlToValidate="PhoneNumber" ID="reqValidatorPhoneNumber" Display="Static" Text="*" /> 
        <asp:TextBox ID="PhoneNumber" runat="server" TabIndex="4" />
                
        <asp:RequiredFieldValidator EnableClientScript="false" runat="server" ControlToValidate="Message" ID="reqValidatorMessage" Display="Static" Text="*" CssClass="errorflag" />
        <asp:TextBox ID="Message" Rows="8" TextMode="MultiLine" runat="server" TabIndex="6" Font-Names="Arial" />
                
        <asp:Button ID="Submit" OnClick="SendEmail" runat="server" TabIndex="7" Text="Send" CssClass="sendButton" />
                
        <p><span class="errorflag">*</span>&nbsp;required</p>
        <asp:RegularExpressionValidator EnableClientScript="false" runat="server" ControlToValidate="EmailAddr" ID="reqValidatorProperEmailAddr" Display="Dynamic" Text="Please enter a valid email address." ValidationExpression="^([0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$" CssClass="errorflag" />

        <asp:Label ID="QuickEmailNotification" runat="server" />
      </fieldset>
    </ContentTemplate>
  </atlas:UpdatePanel>
</asp:Panel>

Let's look more carefully at just the markup for the button that is used to submit the form.

ASP.NET
1
2
<asp:Button ID="Submit" OnClick="SendEmail" runat="server" TabIndex="7" Text="Send" CssClass="sendButton" />

When clicked, that button forces the page to post back to the server and run the click handler called SendEmail. Remember, though, that we've surrounded our form elements, including the submit button, with an <atlas:UpdatePanel> tag. So, the post back doesn't end up causing the usual flash in the browser for the end user. Only a tiny portion of the page must refresh provide feedback about whether or not the email was ultimately sent or encountered a problem.

For those who are extra curious about how the SendEmail function works, it's crux uses this logic:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MailMessage MyMail = new MailMessage();
MyMail.To = "\"Groovy bits Info\" <info@groovybits.com>";
MyMail.From = "\"Groovy bits Website\" <info@groovybits.com>";
MyMail.Subject = "Gb website inquiry";
MyMail.Body = "<p style='font:Arial;font-size:10pt;'>" +
              Message.Text +
              "<hr>Name: " + YourName.Text +
              "<br />eMail address: " + EmailAddr.Text +
              "<br />telephone number: " + PhoneNumber.Text +
              "</p>";
MyMail.BodyFormat = MailFormat.Html;

// Mail server at GoDaddy.
SmtpMail.SmtpServer = "relay-hosting.secureserver.net";

To use the SmtpMail class, we've configured our SMTP settings in this site's web.config file:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0"?>
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

  [removed for brevity]

  <system.net>
    <mailSettings>
      <smtp from="info@groovybits.com">
        <network host="somecomputer.someserver.net" password="removedForObviousReasons" userName="info@groovybits.com" defaultCredentials="false" port="25" />
      </smtp>
    </mailSettings>
  </system.net>

</configuration>
}

TimerControl

The Groovy bits portfolio is presented as a slide show.

The text and other data for each slide is stored in an external XML file that looks like this:

XML
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8" ?>
<Portfolio>
  <Project Name="client-name-1" Text="We provided ... for this site." ImageUrl="~/Portfolio/screenshot1.jpg">
    <Highlight>this-n-that</Highlight>
  </Project>
  <Project Name="client-name-2" Text="We provided ... for this site." ImageUrl="~/Portfolio/screenshot2.jpg">
    <Highlight>this-n-that</Highlight>
  </Project>
</Portfolio>

The <asp:FormView> is a great way to show record at a time from a multi-record data source like this XML file. Since any <Project> can have multiple <Highlight> tags, the <ItemTemplate> in the <asp:FormView> uses a <asp:Repeater> to loop over the highlights (obtained with a XPathSelect statement).

ASP.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<asp:FormView ID="SlideShow" Runat="server" DataSourceID="PortfolioDS" HeaderText="Some of our clients..." AllowPaging="True" CssSelectorClass="SlideShow">
  <ItemTemplate>
    <div class="ProjectThumbnail">
      <asp:Image runat="server" ImageUrl='<%# XPath("./@ImageUrl") %>' />
    </div>
    <div class="ProjectDescription">
      <asp:Label runat="server" Text='<%# XPath("./@Name") %>' CssClass="ProjectName" />
      <asp:Label runat="server" Text='<%# XPath("./@Text") %>' CssClass="ProjectText" />
      <asp:Repeater DataSource='<%# XPathSelect("Highlight") %>' runat="server">
        <HeaderTemplate><ul></HeaderTemplate>
        <ItemTemplate>
          <li><%# XPath(".") %></li>
        </ItemTemplate>
        <FooterTemplate></ul></FooterTemplate>
      </asp:Repeater>
    </div>
    <div class="clearing"></div>
  </ItemTemplate>
</asp:FormView>
<asp:XmlDataSource ID="PortfolioDS" DataFile="~/App_Data/Portfolio.xml" runat="server" XPath=".//Portfolio/Project" />

To help this <asp:FormView> avoid producing <table> tags, a control adapter is used. For details, you can read more about this site's use of CSS Friendly control adapters.

The <asp:FormView> is wrapped in an <atlas:UpdatePanel> triggered to move to the next slide using an <atlas:TimerControl>.

ASP.NET
1
2
3
4
5
6
7
8
9
10
11
12
13
<atlas:TimerControl runat="server" ID="timer1" Interval="4000" OnTick="GoToNextSlide" />
<atlas:UpdatePanel runat="server" ID="up1">
  <ContentTemplate>
    <asp:FormView ID="SlideShow" Runat="server" DataSourceID="PortfolioDS" HeaderText="Some of our clients..." AllowPaging="True" CssSelectorClass="SlideShow">
      <ItemTemplate>
        [removed for brevity]
      </ItemTemplate>
    </asp:FormView>
  <asp:CheckBox runat="server" ID="Automate" Text="Advance automatically" Checked="true" />
 </ContentTemplate>
</atlas:UpdatePanel>
<asp:XmlDataSource ID="PortfolioDS" DataFile="~/App_Data/Portfolio.xml" runat="server" XPath=".//Portfolio/Project" />
C#
1
2
3
4
5
6
7
8
protected void GoToNextSlide(object sender, EventArgs e)
{
    if (Automate.Checked)
    {
        SlideShow.PageIndex = (SlideShow.PageIndex + 1) % SlideShow.PageCount;
    }
}
© 1999-2008 Groovy bits