Steve Wellens

Programming in the .Net environment

Sponsors

Links

May 2009 - Posts

Quick and Dirty Automatic Page Navigator

Over on the Asp.Net forums, a user asked how to store an ordered list of pages in an Xml file so he could use it to control the navigation of a group of pages on his web site. It was for a 'Wizard' where the user needed to go through the pages in sequence…no jumping directly to a page. He wanted it to be easily editable.

I thought about it and decided that Xml was overkill. There is no hierarchical data structure required. A simple text file would suffice (although it's not as sexy as Xml is) and there are more text editors than Xml editors….long live Notepad!

I thought about it some more and realized that once the contents of the text file was read in, it should be cached so that each page with the navigation control didn't have to reread it.

Yet more thought: Why put the list of pages in a separate file? If you are going to make a navigation control, why not put the list into the control where it's used? True, it's not a generic control in a traditional sense, but if you are going to have to edit the list of pages anyway, why not do it in the control?

Quick and dirty can be bad, but simple is always good…yah?

Well one thing led to another and I ended up writing a page navigation user control:

                                    

Features:

Pages are easily removed and added to the array of strings in the control (edit the file). 

The array of strings (pages) is static so there is only one copy in memory.

Navigation is automatic.

The Prev and Next buttons are automatically enabled and disabled.

Usage:

Edit the list of pages in the control.

Drop the User Control on each page to be navigated...or drop it on a Master Page.

Here's the UC_Navigator.ascx file (UC is for User Control).  It's basically two buttons in a div.

<%@ Control Language="C#" 
    AutoEventWireup="true" 
    CodeFile="UC_Navigator.ascx.cs"
    Inherits="UserControls_UC_Navigator" %>
 
 <div style="border-style:ridge; 
     border-width:medium;     
     padding: 5px; 
     background-color:#808080
     text-align:center; 
     width: 134px; 
     vertical-align: middle;">
    <asp:Button ID="ButtonPrev" runat="server" OnClick="ButtonPrev_Click" Text="Prev" />
    &nbsp;&nbsp;&nbsp;
    <asp:Button ID="ButtonNext" runat="server" OnClick="ButtonNext_Click" Text="Next" />
</div>

Here's the code-behind file:

using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;
 
public partial class UserControls_UC_Navigator : System.Web.UI.UserControl
{
    // list of pages to navigate - in order
    // page names must be in lower case
    static String[] Pages =
    {
        "default.aspx",
        "child1.aspx",
        "exportcalendar.aspx"
    };
 
    enum DIRECTION { PREV, NEXT }
 
    // ---- Page_Load ----------------------------
    //
    // Disable Prev/Next buttons when appropriate
 
    protected void Page_Load(object sender, EventArgs e)
    {
        int CurrentIndex = GetCurrentIndex();
 
        if (CurrentIndex == 0)
            ButtonPrev.Enabled = false;
 
        if (CurrentIndex == Pages.Length - 1)
            ButtonNext.Enabled = false;
    }
 
    // ---- GetCurrentIndex ----------------------------------
    //
    // Gets the index of the current page 
    // (from the array of Pages)
    //
    // returns -1 if the Page isn't in the Pages array
 
    private int GetCurrentIndex()
    {
        String CurrentPage;
 
        // get the current page the User Control is on (from parent page)
        CurrentPage = Parent.BindingContainer.TemplateControl.AppRelativeVirtualPath;
 
        // get filename only and force lower case
        CurrentPage = Path.GetFileName(CurrentPage).ToLower();
 
        // get the index from the page array
        int CurrentIndex = Array.IndexOf(Pages, CurrentPage);
 
        return CurrentIndex;
    }
 
    // ---- MoveToPage ---------------------------------
    //
    // Moves to previous, or next page if possible
 
    private void MoveToPage(DIRECTION Direction)
    {
        int CurrentIndex = GetCurrentIndex();
 
        if (Direction == DIRECTION.PREV)
        {
            if (CurrentIndex == 0)
                return// can't move before first page
 
            //Response.Redirect(Pages[CurrentIndex - 1]);
            Server.Transfer(Pages[CurrentIndex - 1]);
        }
 
        if (Direction == DIRECTION.NEXT)
        {
            if (CurrentIndex == Pages.Length - 1)
                return// can't move after last page
 
            //Response.Redirect(Pages[CurrentIndex + 1]);
            Server.Transfer(Pages[CurrentIndex + 1]);
        }
    }
 
    // ---- Navigation buttons ---------------------------
 
    protected void ButtonPrev_Click(object sender, EventArgs e)
    {
        MoveToPage(DIRECTION.PREV);
    }
    protected void ButtonNext_Click(object sender, EventArgs e)
    {
        MoveToPage(DIRECTION.NEXT);
    }
}

A few problems I had on the way:

Getting the current page the user control was on took a bit of digging around. I ended up using the AppRelativeVirtualPath property. It works for normal pages and child-of-master pages.

The first time AppRelativeVirtualPath was accessed, it returned the page in mixed case: subsequently it returned the page in all lower case. I decided to make everything lower case (remember the article is prefaced with Quick and Dirty J). 

I wasn't sure which to use, Response.Redirect(…) or Server.Transfer(…). Both worked so I left them both in (one is commented out).

Possible Enhancements:

I like the Spartan functional look…plain gray buttons. You could however, "sexy up" the control with CSS and graphics. For sure dude, this ain't purty:

                   

Adding First and Last buttons would be trivial.

You could make the control more "generic" by keeping the list of pages in a separate file or a database and reading them in once (do it in a static constructor).

You could add a property to switch between using Response.Redirect(…) and Server.Transfer(…) methods.

You can download the code Here.   My pithy advice:  Think before you type.

I hope someone finds this useful.

Steve Wellens

jQuery…Worst Practices

jQuery and Regular Expressions have a lot in common: They are both amazingly powerful and they are both as ugly as a knife fight between two obese street-whores in a garbage-strewn alley.

There isn't much hope for Regular Expressions. They are what they are. I use a freeware program called Expresso which can parse regular expressions and provide text descriptions for them.

But for jQuery, there is hope. You can use this powerful tool and still create code that is readable and maintainable. It takes a bit more work, and a bit more thought, but it is worth it.

Caveat: I am not a jQuery expert; I am not a JavaScript expert. Not even close. But I know the difference between good code and bad code: Good code is easy to understand.

 

Worst Practice #1:  Hooking up event handlers dynamically when there is no need.

In the document ready event, some developers search for a DOM object and connect an event handler to it as follows:

<script type="text/javascript">
    $(document).ready(function()
    {
        $("#Button1").click(function(event)
        {
            alert("You Clicked Me!");
        });
    });
</script>

Why add indirection? Why move the event hookup away from the object? It is much more straightforward and object oriented to do it the "old fashioned" standard way:

<asp:Button ID="Button1" 
            runat="server" 
            Text="MyButton"                
            OnClientClick="alert('You Clicked Me!');" /> 

Of course, there are reasons for doing it with jQuery.  You might want to change event handlers dynamically depending on some condition in the page.  If you need to assign a common event handler to several objects on a page, it makes sense to do it in one place. As a matter of fact, that's the beauty of jQuery.  But for a single event on a single control….NO.

Just because you can do something doesn't mean you should do it.

 

Worst Practice #2:  Chaining the results.

A touted feature of jQuery is the ability to chain results as in this example:

$("div.highlight").css("color", "yellow").css("background-color", "black").css("width", "50px");

Let's make it more readable and more maintainable. It's easier to work with many short lines than a single monolithic monstrosity:

var Divs = $("div.highlight");
 
Divs.css("color", "yellow");
Divs.css("background-color", "black");
Divs.css("width", "50px");

Isn't that better? In the real world, hopefully you would use a CSS class to format the divs.

By the way, putting everything on one line does not make it run faster.  This is so important I'm going to repeat it:

Putting everything on one line does not make it run fasterDon't believe me? Here's proof:

$(document).ready(function()
{
    for (i = 0; i < 1000; i++)
    {
        Test1();
        Test2();
    } 
});
void function Test1()
{
    $("div.highlight").css("color", "yellow").css("background-color", "black").css("width", "50px");
}
void function Test2()
{
    var Divs = $("div.highlight");
 
    Divs.css("color", "yellow");
    Divs.css("background-color", "black");
    Divs.css("width", "50px");
}

Here are the results from the IE 8 Profiler:  The one-line code took longer. 

If you run the test several times, the results vary slightly but the conclusion is that there is no performance gain chaining lines together...there is only a loss of maintainable code.

 

Worst Practice #3:  Lack of comments.

jQuery is cryptic. If any code ever screamed out for comments it is jQuery code. Let's raise the professionalism of the above code:

// get all the Divs with the highlight class and
// format them and set their width
 
var Divs = $("div.highlight");
 
Divs.css("color", "yellow");
Divs.css("background-color", "black");
Divs.css("width", "30px");                        

Isn't that better than the original cryptic single-line? It doesn't take that much extra effort to produce good code.


If you work with technically proficient developers, you'd better not write sloppy code or you'll find yourself reviewing documentation in a tiny cubicle next to a loud-voiced sales person who uses their speaker phone for everything…including checking voice mail.

If you work for a large, non-technical company, chances are that no one gives a rat's ass what you do and you can get away with writing sloppy code.

If you work by yourself in a small company, no one is going to be evaluating your code and you can get away with writing sloppy code.

However, hopefully, a sense of self-pride will compel you to write good code….not just code that 'works'.

 

In summary, jQuery is great. It can save a lot of time in initial development. But if you don't take care, the productivity gains will be lost by having code that is difficult to understand and therefore difficult to maintain.

 

// send friendly message to all jQuery Developers 
$("jQuery.Developers").HappyProgramming();
 

Steve Wellens

More Posts