Thursday, December 31, 2009

How Best to Keep a Treeview in Sync with SQL Server?

I've run into a bit of a challenge with my work and thought I might be able to get some good advice by drawing attention to it here.  I've outlined the problem quite succinctly here.

If you have any Best Practice ideas based on your own work, please do leave a comment if you can.  Thanks!

Thursday, November 19, 2009

Syntax Highlighter

I'd like to send a shout out & much thanks to Nilesh Thakkar, a software engineer at SIHL in India for recommending I introduce Syntax Highlighter into this blog.  It provides a super simple way to display source code to fellow developers.  You'll see it used in all future posts.

Incidentally, for anyone else writing a blog on Blogger, Matthew Ball has provided a step-by-step explanation of how to add Syntax Highlighter.

Thank you, Nilesh!!

Wednesday, November 18, 2009

Encoding & Decoding Bit-Sums For a CheckBoxList

Have you ever had occasion where you need to allow users to select one or more items in a list? With ASP.Net a good UI control to do that with is the CheckBoxList. Here's a pair of examples:



Then the question arises, how do you store their selections?  If the potential list is quite long then you're probably going to want to use a CDF string of the values you've assigned to each list item - example: "2,3,11,17,34"

But for smaller, more finite lists a good way to go is with bit-encoded sums.  You assign each list item a base-2 value, such as:
  • Item A    2^0 = 1
  • Item B    2^1 = 2
  • Item C    2^2 = 4
  • Item D    2^3 = 8
In this way, when the user has finished their selection, you can add up the associated values, store that sum, and then decode it later when you return to the page again.  Thus for the aforementioned example, if the user selected A, C, & D, the sum would be: 1 + 4 + 8 = 13.

Encoding the values is very straightforward.  Here's a little algorithm that does the job:
int newSum = 0;
    foreach (ListItem item in checkBoxList.Items)
    {
      if (item.Selected)
        newSum += Convert.ToInt32(item.Value);
    }

Decoding though is somewhat trickier.  I imagine there are many possible approaches.  Here's a method I created to do the job:
private void SelectCblItems(CheckBoxList cbl, int sum)
  {
    // Determine the largest Base-2 Power that comprises sum
    int maxPower = (int)Math.Log(sum, 2);
    for (int pow = maxPower; pow >= 0; pow--)
    {
      int partSum = (int)Math.Pow(2, pow);
      if (partSum <= sum)
      {
        ListItem listItem = cbl.Items.FindByValue(partSum.ToString());
        if (listItem != null)
          listItem.Selected = true;

        sum -= partSum;
      }
    }
  }

Sunday, September 20, 2009

Lessons Learned from Setting Up One's Own Test Server

I've had occasion to set up a simple test server on which to test an AJAX enabled ASP.Net web app. 99% of the time there'll be just me or a client (in a distant location) testing it. Knowing that this load was quite small, I felt confident using an old P4 machine with Windows XP.

But lo and behold, strange things started occurring. At seemingly random times the app would hang. This was most often noticeable when I was opening a treeview and additional nodes were being loaded on demand. It was strange though because this never happened within the Visual Studio 2008 debug environment. It also never occurred when I opened a browser on the test server itself and connected via the localhost. So what was the problem?

At first I thought it had something to do with the fact that from my workstation I was going out to the Internet and then coming back into the test server. This would explain an increased latency (lag time) but not necessarily explain why it was timing out so frequently.

Eventually, through the help of ASP.Net forums contributor Jeffery Tay, I found the answer! When I mentioned to him that I was using Windows XP on the test server he immediately clued me into the fact that the default number of connections for XP was just 10. Because multiple connections are frequently opened for even simple operations, this might explain why the server appeared to be hanging. After a little research I found this useful discussion. It showed me how to bump up the connection limit to 39.

Only time will tell if this is a complete & final solution but so far it appears to have resolved everything!

Friday, September 18, 2009

Frustrating Deployment Experience!

I've deployed a few simple AJAX-enabled web pages to a local server that is accessed through the Internet by just two people: me & a client.

When running locally in the VS2008 environment it works perfectly fine but when accessed through this server there are frequent and mysterious timeouts. There appears to be no pattern to when & why they occur. Sometimes I get strange errors with codes in the 40000 & 50000 range. Other times it just hangs. There is so little load on this server that I do not understand why this is occurring.

I suspect this is an AJAX related problem but am not sure of that. I'm wondering if anyone else has experienced something similar and might have some tips about how to resolve it?

Thursday, August 27, 2009

Find All Occurences of a Specific Field Name in SQL Server

Here's a useful T-SQL script that will identify every table that has a field with a specific name:
select sysobjects.name
from syscolumns
left join sysobjects on sysobjects.id = syscolumns.id
where syscolumns.name like 'myFieldName'
order by 1
Source - Steve Gray

Wednesday, August 26, 2009

Developer AutoLogin & Redirect

A large project I'm working on these days includes a Login page which each user must pass through before being able to access the rest of the site. There is code similar to this that prevents unauthorized entry onto every page:












For some time I would just comment out this code until the rest of the module was complete. The problem with that is that as the application became more & more sophisticated, there was assorted initialization code elsewhere that has to be executed each & every time.

For the Run-Debug cycle it became exceptionally time consuming (and annoying) to manually go through the entire login process every time. Here's an example of the steps involved:







































Oh sure, it's just a few keystrokes and mouse clicks each time but repeated dozens (or hundreds!) of times per day, one soon hopes for a way to automate the process. And I found it!



In Visual Studio's Solution Explorer, right-click on whatever page you're working on and choose "Set As Start Page" from the popup menu. Note: You've probably already done this!

Now in the Page_Init event handler of your Master Page (FYI all my projects use Master Pages) add this code:

protected void Page_Init(object sender, EventArgs e)
{
if (!IsPostBack)
{
// If we're on a development machine then record the Start Page that Visual Studio is set to
string currPage = Page.ResolveUrl(Request.ServerVariables["URL"].ToString());
if (System.Diagnostics.Debugger.IsAttached && SessionData.GetCurrentUser() == "")
if (SessionData.GetSessionObject("CurrentDebugPage") == null)
SessionData.SaveSessionObject("CurrentDebugPage", currPage);
}
}

Then in the Page_Load event handler of your login page add this code:

// Note: This must be set above: using System.Web.Security;
protected void Page_Load(object sender, EventArgs e)
{
// This tests whether we're running on a development machine. If so, we'll automatically login.
if (System.Diagnostics.Debugger.IsAttached && SessionData.GetCurrentUser() == "")
{
string currUser = "your_username";

FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(currUser, false, 5);
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
Response.Cookies.Add(authCookie);

// If a specific page has been set as the Start Page then redirect to it; otherwise go to the Default page
string currDebugPage = SessionData.GetSessionObject("CurrentDebugPage").ToString();
HttpContext.Current.Response.Redirect((currDebugPage == null) ? "~/default.aspx" : currDebugPage, true);
}
}

Note: The various SessionData methods are just simple ones I've constructed to ease the process of storing & retrieving variables in the Session State. I will gladly provide them upon request.



The result is that now, every time I run my project in Visual Studio, it jumps right to the desired web page, logging me in automatically. And yet when run in non-Debug mode it works normally. So I don't have to alter any code whenever I release a new production version.

In case you're wondering how this works, the process is quite straightforward:
  1. The Master page's Page_Init event handler is run before every Content page's Page_Load event handler. It records the start page that the developer wanted to go to, placing this string into a Session variable.
  2. When that desired page's Page_Load event handler is initially entered, the security checks therein reject entry because the user is not logged in. They then redirect to the Login page.
  3. In the Login page the special test in Page_Load passes because the code is being run from Visual Studio and because the user is not yet logged in.
  4. A FormsAuthenticationTicket is instantiated and then placed in a Cookie. This allows one to artificially login through the ASP.Net Login mechanism.
  5. The desired start page, originally stored by the Master Page, is retrieved from the Session State and control is redirected to that page.
  6. This second time the security checks in the desired page's Page_Load event handler pass because the user is now officially logged in.
It actually works really well and will save you a lot of time! If you have any questions, please just leave a comment.

Monday, August 17, 2009

A "Subtle" MessageBox

For years now, whenever I wanted to display a message box to the user I'd use some variation of the following:

// Displays an alert message box to the user.
public static void ShowMessage(string msg)
{
msg = Tools.FixJavaScriptString(msg);
Page page = HttpContext.Current.CurrentHandler as Page;
ScriptManager.RegisterStartupScript(page, page.GetType(),
Guid.NewGuid().ToString(), "alert('" + msg + "');", true);
}

// A single apostrophe is not allowed in a SQL string
// on its own so we need to prefix it with a backslash
.
public static string FixJavaScriptString(string text)
{
string text2 = text;
if (text.IndexOf("'") != -1)
text2 = text.Replace("'", "\\'");

return text2;
}

This works fine but the drawback is that the user must press the OK button on every Alert box.

Inspired by a feature I first noticed in FaceBook, I decided to create a MessageBox that would appear but then fade away after a developer-defined period of time. You can see it in action in this video:


The solution works with all of the following, which I use in most of my web development work:
  • The ASP.Net AJAX UpdatePanel
  • C#
  • jQuery
To get it working, first you must add this markup code to every page you wish to display a Subtle MessageBox on:




I always place this code right near the bottom of every Content page, just above "".

Then add the following server-side code to your web app (I have it in a shared code file called "Common.cs") :

// Displays a message box that disappears on its own.
public static void ShowSubtleMessage(string msg, int duration, string[,] cssParams)
{
msg = Tools.FixJavaScriptString(msg);
string script = "$('div#msg').empty().html('" + msg + "'); $('div.subtleMsg')";

if (cssParams != null)
{
string property = "";
foreach (string cssItem in cssParams)
{
if (property == "")
property = cssItem;
else
{
string propVal = cssItem;
script += ".css('" + property + "', '" + propVal + "')";
property = "";
}
}
}

Page page = HttpContext.Current.CurrentHandler as Page;
script += ".fadeIn(1000).animate({ opacity: 1.0 }, " + duration.ToString() + ").fadeOut(2000);";
ScriptManager.RegisterStartupScript(page, page.GetType(), Guid.NewGuid().ToString(), script, true);
}

Here is the CSS code that I use:

div.subtleMsg
{
display:none;
float: left;
position:absolute;
left:360px;
top:220px;
width:300px;
height:100px;
background:#0e90e6 url(../Images/Gradients/blueRectGradient4.jpg);
text-align:center;
padding:10px;
font-size:16px;
font-weight:bold;
color:White;
border:ridge 5px darkgray;
}


And here's a typical call to the method:

string msg = "Blank descriptions are not allowed!";
Common.ShowSubtleMessage(msg, 2000, new string[,] { { "left", "470px" }, { "top", "310px" }, { "width", "330px" }, { "height", "60px" } });



There are several improvements that could be made:
  • The HTML markup code could potentially be created with jQuery, via a call from the server-side code. I tried to do this but after several hours gave up. Perhaps someone reading this will come up with a solution!
  • Potentially the minimum required dimensions of the message box could be automatically calculated using some method in the .Net library.
But I think it's a good start and it works well for me! You can download a copy of everything here.

Tuesday, June 16, 2009

IPostBackHandler and EnableViewState

I've spent several hours troubleshooting a crazy bug, which is explained in detail here. In a nutshell, the changed state of toolbar buttons (ASP.Net LinkButton controls) was not being kept. Every time a partial postback occurred on the web page, the changes would disappear.

I did much research and learned that the IPostBackHandler mechanism is what was responsible for restoring the control values after each postback. For some reason it didn't appear to be working. I even built a small test project but it did work in that!

Suddenly I noticed that the test page in my main project had EnableViewState=false. I changed it to true and ... everything worked fine!

My findings seem to conflict with several articles, including this one. All I know is that in my case EnableViewState must be set to true.

I'm posting this, both as a future reminder to myself and in the hopes that a State Management guru will read it and explain to me what's really going on.

Thursday, June 11, 2009

pageLoad() and $(document).ready()

I just read a pair of articles by Dave Ward over at his excellent Encosia site. This one focuses on the differences between pageLoad() and $(document).ready() and this one focuses on having a pageLoad() function in both a Content page and a Master page.

If you're not 100% familiar about when these functions fire (and don't fire) then I'd strongly recommend reading both articles.

Tuesday, June 9, 2009

Cacading Events in a Master-Content Page Project

I came across a situation where I needed to add a toolbar to a Master Page and monitor the toolbar button events from the Content Pages. I did a bunch of research and came across this article. It explains what one must do to accomplish this.

One key necessity is to add a directive similar to this on every content page:
<%@ MasterType VirtualPath="~/main.Master" %>

Then in the Master Page you have to define an event handler similar to this:
public event EventHandler CommandButton_ClickHandler;

When a button in the Master Page is pressed, you explicitly fire the event like this:
protected void CommandButton_Click(object sender, EventArgs e)
{
CommandButton_ClickHandler(sender, e);
}

Finally, in every content page you have to wire up the event handler mechanism:
protected void Page_Init(object sender, EventArgs e)
{
Master.Toolbar_ClickHandler += new EventHandler(CommandButton_Click);
}

Whenever a button in the Master Page is pressed, it causes the event handler you setup to fire, which in turn is monitored by the listener in the content page. It's all quite simple but you have to get the syntax just right.



I created a small proof of concept project, which includes buttons directly located in a Master Page, as well as another example of buttons in a toolbar, which in turn sits in a Master Page. You can download this project here. For the first approach, set "default.aspx" as the start page. For the second approach, use "default2.aspx" instead. Hopefully this will help you implement this powerful concept in your own work!

Sunday, June 7, 2009

Review: Windows 7 - 64 bit Release Candidate

For those seeking unbiased independent feedback on the upcoming Windows 7, these comments are not mine but come from a tech savvy friend/colleague:

Windows 7 RC 64-bit – Notes:

- One thing I noticed right off the hop was that it went out and automatically went looking for drivers for the hardware in my laptop. It must have reported my configuration back to Microsoft and then automatically downloaded whatever it had been able to identify. I have not done much with it yet, but I can see that it is more responsive. Total memory load after initial boot (with nothing else installed on the system) is 16 % of 4094 (4 GB total RAM) or approx. 650 MB, this compares with about 900 MB on my Vista system (same platform).

- Memory footprint is now sitting at 1 GB just about all the time, surprisingly it remains quite responsive but don't even think of running this on less than a dual-core.

- There is no email client installed by default, you can go to the MS site and download live mail. They cut out a bunch of "default" applications (messenger etc. – although I don’t use it anyway).

- It now includes a screen capture tool, obviously not as full featured as SnagIt but still functional.

- The Windows side bar from Vista and any screen gadgets are not loaded at startup by default, although if you get a system from Dell or HP I imagine that they will add their own tweaks to the interface.

- They are trying to make the computer even easier to use in the future (people will not have a clue how to do anything). I suppose many people just want that to be the way their computer works (just make it work and I don't give a S**T what goes on underneath). This was clearly evidenced by the fact that it went out looking for drivers for my system without my intervention. I suppose a Mac works the same way, how many people know that Unix is sitting under the hood, not your average user anyway.

- Everything looks very like Vista. Taskbar is different and you can pin shortcuts to it. They have added a glow effect when you mouse over.

- File copying still is not as fast as XP, but improved from Vista.

- Boot time is quicker than my XP desktop and recovery from sleep is VERY quick.

- Installed 32-bit Adobe Photoshop and LightRoom - no problem.

- Installed 64-bit Java but Adobe Flash is not available in 64-bit yet, so you need to run two different browsers to get full functionality (Youtube and Java sites).

- After system boot RAM is at 837 MB and the system is usable, although there is still stuff going on in the background and if you let the system settle down for a few minutes the RAM is then at 971 MB.

- Ran a virus scan (Avira - free) on schedule, but the memory consumed by the application did not release after the scan was complete (then at 1225 MB).

- Installed Firefox – no problems.

- If you want to install the applications that MS cut from the install (mail client, messenger etc) then you have to install "Live Essentials". If you select everything this loads a HUGE suite of applications, you can be selective, but I went to see what it wanted to load with just the mail client and it was more than I wanted on the system. I think in such cases it would be better to load individual "other" applications to fill the gap (Thunderbird, Picasa etc.). Although I think it would depend on the user that I was doing this for.

- I was looking at pricing today as the download from MS is Windows 7 Ultimate version. There is no pricing announced, but Vista Ultimate was selling for $591......wow !! I could not see paying anything even close to that for an operating system and it "aint that good".....at least not $600 worth of good.

- General observation on installing software - there is a very definite pause before anything starts to happen, in fact you might even think that things had froze. The user account control is way less annoying (I guess they must have listened there also), but still pops up if there are changes going to happen to the system (like installing software). I tried installing SnagIt, but when it got to installing the printer driver the whole process got stuck consuming 50 % of the CPU and I had to kill everything and reboot to finally clear it.

- By the way I have not tweaked this installation at all and everything that was "running out of the box" is still running (like indexing, defender etc.). I decided to leave it as is and just see how it performs. So far it seems to do quite well, despite the huge amount of RAM that it is consuming. I see several comments on the web about people calling this "what Vista was supposed to be". Vista never should have shipped.

- Windows 7 is looking like a definite improvement on Vista. Today there was a flag in the notification area that wanted me to setup a backup. I decided to play along and it walked me through choosing a destination. It gives all sorts of hints and links to get more information. Once I had chosen a backup location (my FreeNAS backup server) it looked at it and said that it was visible to other users and was this okay. We proceeded and I let it go with whatever defaults it wanted to save, which included making a full disc image as well as saving all of my documents and settings. It then prompted and said to access the disc image you would have to create a rescue cd or dvd, I clicked that link and it walked me through the process of making a rescue disc.

- When you look at these things that they have thrown in, such as: screen capture, decent backup utility, screen notes etc......plus whatever I have not come across yet, it looks like maybe they are starting to get the right idea (after how many years......). I suppose I should try and find a feature list somewhere so I can take full advantage of this "test drive".

- Had a spare 2 GB high speed SD card so I decided to add that to the system and let it use it as “Ready Boost”. Things seem to be snappier, but I am not sure if that is real or imagined. My system is quick and does have good specs. (Core 2 Duo 2.1 GHZ with 4 GB RAM). I will leave it in for a few days and then remove it and see if I notice any change.

- Could not install Zonealarm Security Suite – not ready for Windows 7 yet.

- Regular virus scan just started and the system is now consuming approx. 1750 MB RAM. Max RAM consumed during this process was as high as 1975 MB – ouch !! Scan completed and it failed to release the RAM again – now sitting at 1941 MB – obviously a reboot will fix that, but that is not right. Seeing as other applications do not do this it has to be the Avira anti-virus that is causing this.

- I did have one unidentified item in Device Manager, this is now resolved (but not by me). I have to assume that one of the updates from MS fixed this.

- Not all of the themes are enabled but can be added. There are different themes for different countries (US, Cdn, South Africa, Aus, UK) – I like the Cdn. theme.

Wednesday, June 3, 2009

ThickBox Demo Project

As a follow-up to my recent postings about ThickBox, I've created a demonstration page, which succinctly summarizes my work on ThickBox to date. It also provides some improvements over what I've written previously. Plus, you're able to download all of the code as well, which should especially help people learning jQuery and ThickBox anew.

Sunday, May 31, 2009

Further Thoughts on ModalPopupExtender to ThickBox Conversion

I added an important paragraph to my earlier post on this subject, which reads as follows:

Let me also say from the outset that not all dialog boxes can be converted over easily. To date I have not found a way to get rid of the Modal Popup Extender if the dialog box depends on server-side code for its operation. The basic problem is that if a postback has to occur then the ThickBox dialog box will disappear, at least temporarily, which is unacceptable. Perhaps there's a way around this but I have not yet discovered it. So make sure you have a good backup before you proceed with any conversion attempt!

ThickBox was clearly not created with ASP.Net in mind. My conclusion is that it works great for simple dialog boxes. You'll end up with a smaller footprint, which will speed up your app. But if there's something more sophisticated necessary, whether it be more complex validation or any server-side code that needs to be run, then I do believe that the ModalPopupExtender is the preferred choice.

I hate the idea of having to include both jQuery and the AJAX Control Toolkit but I don't see a way around it at this time. Perhaps a developer, much more Javascript savvy than me, will build a jQuery Plugin that will truly replace the ModalPopupExtender!

Thursday, May 28, 2009

Converting the ModalPopupExtender to ThickBox

As a follow-up to my two articles on using jQuery's ThickBox plugin instead of the AJAX Control Toolkit Modal Popup Extender, I thought it prudent to provide a checklist of what steps are needed, as I've even forgotten some myself at times!

Let me also say from the outset that not all dialog boxes can be converted over easily. To date I have not found a way to get rid of the Modal Popup Extender if the dialog box depends on server-side code for its operation. The basic problem is that if a postback has to occur then the ThickBox dialog box will disappear, at least temporarily, which is unacceptable. Perhaps there's a way around this but I have not yet discovered it. So make sure you have a good backup before you proceed with any conversion attempt!



There are several different ways to setup ThickBox in your project and use it in place of the Modal Popup Extender. I'm simply going to describe one approach that works well for me.

1. Reference jQuery and ThickBox within the Script Manager.






2. Configure how Thickbox references the loading animation GIF file. More here in Step 1.

3. Reference thickbox.css. You will likely want to customize this CSS file to suit your particular needs.





4. Update the path to macFFBgHack.png as described in Step 2 here.

5. Remove the entire ModalPopupExtender element in the ASPX file.

6. Change the asp:Panel element that the ModalPopupExtender referenced to a div element instead. Remember to modify the closing tag as well. In what is now the opening div element remove runat="server" and Enabled="false". Then add style="display:none" to this opening div element.

7. The section within the opening and closing div tags define the contents of the dialog box that you're going to display. You can leave them exactly as they were working with the ModalPopupExtender, albeit with one minor caveat:

Any Button controls you had will no longer function. The reason has something to do with the way ThickBox copies & utilizes the markup code. But there is a simple solution: Leave the server-side events as-is. Then add this client-side event handler definition to every Button control: OnClientClick="CloseModalDialog(this)"

8. Since ThickBox will likely be used by many web pages, it makes sense to create a common.js external Javascript file that is referenced by all of them. In practice I have the reference for it in the same ScriptManager definition shown above, though it was removed there for clarity.

In common.js add this CloseModalDialog function:







9. In the ThickBox documentation you'll learn that the "standard" way of initiating the display of a dialog box is via an <a href= reference. That's fine but all of my work to date involves initiating dialog boxes from server-side code. For example, this was previously done like this: ModalPopupExtender1.Show();

To accomplish the same thing with ThickBox I created a server-side method called ShowDialog and placed it in a class library called Common.cs which is referenced universally by all of the web pages in my project. Here is the code:








That's it! There are indeed several steps initially but once you've done it once then successive thickboxes are much quicker to implement.


Note: If you'd like a copy of any code I've referenced, just write me and I'll send it to you!



Important Note re Validation
When fields are displayed on a typical ASP.Net web page, Validation controls are often used to ensure that the necessary fields are complete, have correct data in them, etc. If there is something wrong then when the Submit button is pressed, some sort of visual indication explains to the user what needs to be fixed.

Unfortunately this doesn't work so well when the same fields are displayed in a ThickBox dialog box. Because the aforementioned CloseModalDialog function is called every time, a postback always occurs and dialog box is lost.

So to solve the problem, an intermediary function has to be called to check whether all of the validation tests were successful; if so then continue on and call CloseModalDialog, if not then end the function quietly. In the latter case, the dialog box stay as-is and the ASP.Net validation controls work as expected, showing the user where the problem(s) lie.

In terms of checking the validation tests, I was hopeful that this approach would work, but I couldn't duplicate the author's success. So I used jQuery to simply double-check the validation again. This is not ideal but as of this time I do not have a better solution.









Here are some examples of modal dialog boxes I've successfully implemented with ThickBox:



Sunday, May 24, 2009

Client-Side Progress Indicator

Long ago I learned how to properly wire up the AJAX UpdateProgress control. This works perfectly to display a wait indicator while the AJAX UpdatePanel is performing a partial page postback. That's fine for server-side operations but what about on the client-side?

Lately I've been doing a lot more work on the client-side, in no small part due to my adoption of jQuery as a standard for all of my web programming. What once was always frustrating is now quite fun! As such, I'm in the process of doing the following with all of my Javascript code:
  1. Converting as much as possible to jQuery.
  2. Streamlining everything I can.
  3. Turning custom code into generic, reusable code.
  4. Moving all of the reusable code to common external Javascript files.
The User Manager module in one of my projects includes two different treeviews:
  • One for Roles/Users
  • The other for something proprietary called "Contract Rights"

Both treeviews have an "Expand All / Collapse All" button below them, providing a one-touch way for the user to quickly expand/collapse all the nodes of the treeview. This operation is done entirely with client-side code. Depending on the number of nodes, the delay time can be upwards of 5 seconds. While it's occurring, there was no wait indicator, so I decided to add one.

I thought I could just add the same jQuery code to the beginning & end of the function as I had successfully deployed to the AJAX Event Handler functions, namely:

$("#<%=UpdateProgress1.ClientID%>").css("display", "block");

$("#<%=UpdateProgress1.ClientID%>").css("display", "none");

I tried it and . . . nothing happened. There was no wait indicator whatsoever! After scratching my head for awhile, it occurred to me that the problem may lay with the web equivalent of the DoEvents() necessity that every WinForms developer is familiar with, namely that the UI needs to be given time to catch up. So I did a little research and found this old, but excellent article on MSDN. It confirmed that the Javascript setTimeout() function had to be used for a similar reason to why DoEvents() is needed with Windows programming.

So I moved the bulk of the code within my buttonExpandCollapse_Click function into one called ExpandCollapseNodes. And then I called the latter from the former as follows:

setTimeout(function() { ExpandCollapseNodes(button, hidVar, treeView, waitAnimID) }, 1);

It works beautifully! Note that just a 1 millisecond delay is all that's necessary to cause the wait indicator to display. The code to turn on the wait indicator is executed in the calling function and the code to turn off the display is the very last line of ExpandCollapseNodes.

I'd more than happy to provide a more complete set of the code if it would help anyone. Just ask!

Monday, May 18, 2009

Passing Objects to Javascript Functions

Shifting from WinForms programming to ASP.Net web development was not an easy transition for me. One of the most difficult things was dealing with Javascript for client-side work. The language is poorly typed and way too freeform for my taste. Hence, much of the example code I've seen often resembles a plate of spaghetti.

Now that I've adopted jQuery into my everyday work, building client-side code is a LOT easier but there are still direct challenges with Javascript that have to be overcome. Such a challenge presented itself to me recently.

As might be expected, I've found that many of the Javascript functions I build have applicability on many web pages. So naturally one wants to build them generically and move them to shared external Javascript files. To do this, parameters often have to be passed to a function. If the function is referenced by the setTimeout or setInterval methods and one or more of these parameters is an object rather than a simple value then it gets a little tricky.

Through a lot of trial & error, and with the help of developers on ASP.Net, I figured out how to do this. The key is to pass the IDs of the objects, as the objects themselves cannot be passed. Here's an example of passing a reference to a Telerik RadTreeView and a Hidden Variable:

As you can see in the first set of code, you must pass the ClientID of ASP.Net controls to the Javascript function. Then in the function itself you need to retrieve the object with methods like $find and $get.

Saturday, May 9, 2009

Using ThickBox with Server-side Buttons

For a while now I've been looking for a jQuery equivalent to the Modal Popup Extender. Two key requirements were:
  1. It had to be able to be called from server-side code.
  2. The dialog box itself had to have ASP.Net buttons.
The first requirement proved easy, using code like this:

string script = "$(document).ready(function() {tb_show('Sample Title', '#TB_inline?height=120&width=300&inlineId=sampleContent', null);});";
ScriptManager.RegisterStartupScript(Page, typeof(Page), "", script, true);

But the second requirement proved much more difficult. It seemed that no matter which open source dialog box I tried, it would just not allow server-side events to be fired.

Eventually though, through the help of ASP.Net user PNasser, I found a solution! The key was to add a simple Javascript function call to each ASP.Net button in the dialog box:

OnClientClick="doPostBack(this);"

Which then calls this:

function doPostBack(element) {
tb_remove();
setTimeout('__doPostBack(\'' + element.name + '\',\'\')', 500);
}

That's it! What's happening is that first the call is made to the ThickBox to remove itself. This is precisely the same function that's called when you click on "close" in its title bar or press "Esc".


Then a postback is explicitly called via the __doPostBack function. But it isn't called immediately. Instead, it's executed after a 500ms delay. I don't precisely know the reason why the delay is necessary but am guessing that it provides the ThickBox enough time to fade out and dispose of itself. I experimented with the delay time and found on my computer that as low as 300ms worked. Less than that though and a full postback occurred, which is not the effect one wants on an AJAX-enabled page!

I've created a little demonstration project which you can download here.

Friday, May 8, 2009

Calling ThickBox From Server-side Code

I'm actively engaged in replacing as many of the AJAX Control Toolkit components as I can with jQuery Plugin equivalents.

In terms of replacing the Modal Popup Extender, I did a lot of research and have decided to go with ThickBox. It's simple to use, has been around for some time, and appears to be fairly flexible. For ASP.Net developers there are several good articles about using it. This was one of the best.

Everything I read referred to wiring up a ThickBox to either a hyperlink or button. That's fine but I wanted to see if I could access it programatically, so that I could call it from server-side code, just like I do with the Modal Popup Extender.

So I searched through the ThickBox source code and found this:

function tb_show(caption, url, imageGroup) {//function called when the user clicks on a thickbox link

And here's how I implemented it with C# code:

string script = "$(document).ready(function() {tb_show('Sample Title', '#TB_inline?height=200
&width=400&inlineId=sampleContent', null);});";
ScriptManager.RegisterStartupScript(Page, typeof(Page), "", script, true);




Incidentally, one thing I could not achieve was implementing buttons that called server-side code. When I tested them, the server-side event was not fired. Perhaps someone will figure out a way to do this and leave a comment on here about that.

Thursday, May 7, 2009

IsPostBack for Client-Side Code

In client-side code, I needed to determine whether the pageLoad function was being run the first time the page was loaded or on a subsequent postback. Searching around, many said there was no such function. But then I came across one posting which illustrated that there indeed is ... at least to detect partial postbacks:
function pageLoad(sender, e)
{
if (!e.get_isPartialLoad())
{

}
}

Saturday, May 2, 2009

How to Get jQuery Intellisense Working with VS2008

Intellisense provides great assistance with anyone starting out with a new programming language. Learning jQuery from scratch, I've found this very much to be the case. Oh sure, one can develop in any language without such assistance. In fact, I have fond memories of teaching myself AutoLISP in the "ancient" year of 1990. Back then there was no Windows and the main IDE was the DOS equivalent of Notepad!

Anyhow, things have changed quite a bit since then and I immediately knew it would be great to have Intelisense working for jQuery! There are several steps involved though but through much trial & error I've finally got it working properly. Hopefully others will benefit from my efforts.


How Do You Know if jQuery Intellisense is Working?
Simply go to a location where you'd normally enter Javascript code and type a dollar sign ("$"). A pop-up menu will appear. What it displays gives you an immediate indication of whether jQuery Intellisense is functioning. Here's a development environment where it's not working:

And here's one where it is working:

In the second screenshot, notice that the first item is a single "$". This is positive! If you wanted to test it further, you could type a little more, like: $("div").
Something akin to the following should then appear in the pop-up menu:

Important Note: Every time I first load a project/solution into VS2008, the jQuery Intellisense does NOT work on the initial try!! I have to clear that menu, wait a few seconds and then try again. From then on it works perfectly, showing the single "$" as the first item in the pop-up menu.


Getting jQuery Intellisense Up & Running
  1. Ensure that VS2008 SP1 is installed. (Further info)
  2. Ensure that Hotfix KB958502 is installed. (Further info)
  3. Install the jQuery library into your project. It'll have a filename like "jquery-1.3.2.js".
  4. Install the jQuery Intellisense file into your project. It may very well have a filename like "jquery-1.3.2-vsdoc2.js" but must be renamed to be identical to the jQuery library name, plus "-vsdoc". Thus in this example, it must be renamed to "jquery-1.3.2-vsdoc.js".
  5. Provide a reference to the jQuery library. There are generally two ways to do this, both of which are described below.
  6. In external Javascript files a direct reference to the jQuery Intellisense file must be made. More details are provided below.
That's it. Once this is [properly] done then you can perform the simple test described earlier. I always prefer to shut down Visual Studio and start it up again with everything installed.


Referencing the jQuery Library and the jQuery Intellisense File
I'm a big believer in:
  • Organizing a project's files into as many sub-folders as makes sense.
  • Separating programming code from markup code as much as possible.
This is why my ASP.Net projects have this general structure:

Notice that there's a "Javascript" folder, which contains the jQuery Intellisense file, the jQuery library file, and an external Javascript file. Based on this file arrangement, either of the following approaches will work with an ASP.Net AJAX application:


You might be wondering why there's no reference to the jQuery Intellisense file? Well, as long as it follows the filename syntax shown in Step #4 above then it is automatically detected and loaded.


Accessing jQuery Intellisense in an External Javascript File
As mentioned previously, I like to place as much Javascript (and jQuery) code into external Javascript files (those ending in ".js") as is practical. If you use the same approach then you will face a disappointment if you're expecting Intellisense to work properly in such a file:

No jQuery Intellisense there!

The solution is very simple though. Just add a reference like this to the top of the file:

/// <reference path="jquery-1.3.2-vsdoc.js" />

Then the Intellisense you enjoy in ASPX files will also work in external Javascript files too! Here's an example:



Final Caveat
A little while ago I presented a way to programatically load jQuery entirely from server-side code. It does work and is powerful because a common server-side method could be built and then used in all of your projects. But jQuery Intellisense will not work using that approach; at least not with VS2008. Perhaps that will change in VS2010!

Mea Culpa re jQuery Intellisense

Prior to direct integration into their Visual Studio product, Microsoft has gone to great lengths to make jQuery easy to use with VS2008. More specifically, they've provided a special patch that provides Intellisense support for jQuery development.

Try as I might though, I just couldn't get it installed on my computer. Whenever I tried, it told me that the supported product was not installed on my machine. Naturally I thought this was because I was running the 64-bit edition of Vista, which has caused some minor compatibility problems before.

I was incorrect. Thanks to the persistence of two Microsoft employees, Vishal Joshi and Joe Cartano, I finally realized my error: I did not have VS2008 SP1 installed. I thought I did for several reasons, not the least of which was because it was not appearing in Windows Update. In fact, when I first looked at the About screen to check, I saw "SP1" there ... but only later realized that it was for the .Net Framework v3.5, not for Visual Studio itself! Here's a screenshot showing you what to look for:

If that "SP" (circled in red) is not present then it's not installed.

So I installed SP1. It took about 20 minutes and then I had to restart Windows. After that I ran the patch, which is also known as "Hotfix KB958502". This time it recognized VS2008 and ran perfectly fine.

There is more to do though to get jQuery Intellisense working. I will describe the steps in my next post.

Friday, May 1, 2009

Setting Default Focus to the Correct TextBox in a Login Control

If you're using the ASP.Net Login control then your login page may look something like this:


The username, by the way, was automatically placed into the textbox via a cookie. And though it's not immediately visible in the above screenshot, the cursor is in the Password textbox via the server-side SetFocus() method. The user, upon being presented with this screen, can then just type their password and press Enter.

Now look at this next screenshot:

Not only is the cursor in the Password textbox like before, but the background color of the textbox is automatically highlighted to provide another visual cue. This highlighting was done globally with jQuery with just a few lines of code.

All good so far. But I discovered that the jQuery auto-highlighting did not work when a page was first loaded. I still do not know why but suspect that the server-side SetFocus() method does not raise the client-side 'focus' event.

Solving the problem meant moving the code the server to the client. Here's what worked for me:

<script language="javascript" type="text/javascript">
function pageLoad()
{
$(document).ready(function(){
PrepareDefaultEventHandlers();

var textBoxUserName = $('#<%= Login1.FindControl("UserName").ClientID %>')[0];
if (textBoxUserName.value == "")
textBoxUserName.focus();
else
{
var textBoxPassword = $('#<%= Login1.FindControl("Password").ClientID %>')[0];
textBoxPassword.focus();
}
});
}
</script>

A big thanks to Dave Ward at Encosia for his invaluable help with this!

Monday, April 27, 2009

Using Fiddler with Visual Studio 2008

Fiddler is a great tool but when I tried to use it to monitor a Visual Studio 2008 web app running locally on my Vista 64-bit computer, it did not work.

I searched around some and finally found this blog posting. Within the comments a fellow suggested changing the URL to this syntax:

http://ipv4.fiddler:1234/MyApp...

I assume this is intercepting traffic in between Fiddler and VS2008. Whatever the case, it works!

Sunday, April 26, 2009

Smart Redirection

I have a situation where I have a test server running on my home network. I was using it to host just one application but when I wanted to do so with 2 or more, I ran into a problem because external URLs could only be redirected to the root folder.

After some experimenting I found a simple solution that seems to work very well:

  1. In the root of Inetpub/wwwroot either remove "default.htm" or change the priority order so that "default.aspx" appears first.
  2. Install into this root folder the two files shown below, Default.aspx and Default.aspx.cs
  3. Then with your IP redirection, use this format: http://your_local_IP_address?app=folder_name - Example: http://24.81.19.172?app=MyTestApp

It's not perfect in that it requires one to add the "app" parameter & value but other than that, it works well. If someone has a simpler solution, I'd love to see it!

Here's the contents of the two required files:

Default.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!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></title>
</head>
<body>
<form id="form1" runat="server">
<div>
</div>
</form>
</body>
</html>

Default.aspx.cs

using System;
using System.Web;

public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string targetFolder = "/MyMajorTestApp"; // Default app to run, in case not "app" parameter is provided
if (Request.Params["app"] != null)
targetFolder = "/" + Request.Params["app"];

HttpContext.Current.Response.Redirect(targetFolder, true);
}
}

Friday, April 24, 2009

2 Ways to Load jQuery from an ASP.Net Master Page

If you're getting started with using jQuery in ASP.Net, you'll probably come across a situation where you would like to load it from a Master Page so that it's available globally for all Content Pages. When you do so you'll find that you get assorted errors for different reasons.

After reading many articles and much trial & error I have determined two different approaches to get it working. Note: My preference is to separate all code from the markup as much as possible. So directly in the root of all my projects is a folder called "Javascript". Inside it I always place [at least] these 3 files:
  • jquery-1.3.2.js
  • jquery-vsdoc.js
  • main.js

Approach #1: Entirely from the Markup Page

<head runat="server">
<title></title>
<asp:ContentPlaceHolder ID="placeHolder1" runat="server">
<script src="<%= Page.ResolveUrl("~/JavaScript/jquery-1.3.2.js") %>" language="javascript" type="text/javascript"></script>
</asp:ContentPlaceHolder>
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
<script src="<%= Page.ResolveUrl("~/JavaScript/jquery-vsdoc.js") %>" language="javascript" type="text/javascript"></script>
</asp:ContentPlaceHolder>
<asp:ContentPlaceHolder ID="ContentPlaceHolder2" runat="server">
<script src="<%= Page.ResolveUrl("~/JavaScript/main.js") %>" language="javascript" type="text/javascript"></script>
</asp:ContentPlaceHolder>
</head>

<body onload="$(document).ready(main)">


Approach #2: Entirely from Server-side Code

protected void Page_Load(object sender, EventArgs e)
{
AddScript(Page.ResolveUrl("~/JavaScript/jquery-1.3.2.js"));
AddScript(Page.ResolveUrl("~/JavaScript/jquery-vsdoc.js"));
AddScript(Page.ResolveUrl("~/JavaScript/main.js"));

string jScript = "$(document).ready(main);";
ScriptManager.RegisterStartupScript(Page, Page.GetType(),
Guid.NewGuid().ToString(), jScript, true);

}

private void AddScript(string src)
{
HtmlGenericControl genCtrl = new HtmlGenericControl();
genCtrl.TagName = "script";
genCtrl.Attributes.Add("type", "text/javascript");
genCtrl.Attributes.Add("language", "javascript");
genCtrl.Attributes.Add("src", src);
Page.Header.Controls.Add(
genCtrl);
}


I hope this helps others! Please be aware that most everything above applies generically to all Javascript code, not just jQuery.

Wednesday, April 22, 2009

Upgrading Crystal Reports Files from VS2005 to VS2008

I just upgraded a fairly large project from Visual Studio 2005 to 2008. Everything went fairly smoothly but I did encounter hundreds of errors when I got around to upgrading the Crystal Reports data files.

In VS2005 I have a pair of files for each report:
  • ReportName.rpt
  • ReportName.xsd
where "ReportName" changes accordingly.

In VS2008, when you bring an XSD file into a project it creates 3 other files:
  • ReportName.Designer.cs
  • ReportName.xsc
  • ReportName.xss
I didn't know the purpose of this trio but assumed they were necessary in VS2008.

In point of fact, all the conflicts occured within the ".Designer.cs" files. There are global variables in each one that cause repeat definitions of the same variables, which is not allowed.

So I did a little research and came across this blog entry. I ended up following just the first part, which was to delete the ".Designer.cs" and ".xss" files. Plus, because all the ".xsc" files were empty, I deleted them too.

Lo and behold, I rebuilt the solution but these excess files were never recreated. More importantly, the reports worked perfectly once again!



Update: One problem I've discovered with the above procedure is that you still end up with one warning message per report like this:

The custom tool 'MSDataSetGenerator' failed while processing the file 'Reports\Templates\MTHOBRK3.xsd'.

The application still runs but I don't like any such warning messages to be present. After a little trial & error I found this to be the ultimate solution:
  1. Delete the .xsc & .xss files only.
  2. Replace the entire contents of each .Designer.cs file with the following:
#pragma warning disable 1591

namespace Website.Reports.Templates
{
public partial class NewDataSet
{
}
}

#pragma warning restore 1591

Thursday, April 9, 2009

Using runat="server" with HTML Elements

ASP.Net developers know that in their markup pages they can use a combination of HTML Elements and ASP.Net Controls. The syntax is mildly different but there is one major distinction:

ASP.Net Controls always include the following: runat="server"

Whereas HTML Elements do not. Or at least that's what I thought!

I have a web app that utilizes a left menu to display modules to the user:

It works great, but before the user is logged in, it makes no sense to display it. Up until now I was just hiding the menu itself. This was fine but what was left was the table column behind. This is what it looked like:

What I really needed to do was hide the "td" element that contained the menu. But since it was pure HTML, I didn't think it was possible to access it from the server-side C# code.

How wrong I was! In fact, all I needed to do was add the runat="server" parameter and voila, I could access the element from C# using the "FindControl" method.

Now the entire space devoted to the menu is hidden and it looks much better. Sure it's just a little thing but sometimes those little UI improvements are what's most important to end users.

Tuesday, April 7, 2009

jQuery

Last night I attended a great talk about jQuery given by Rod Paddock of Dashpoint Software. He was up from Austin, Texas and was a very entertaining speaker. Here's a summary of his talk.

This was part of the monthly speaker series organized by the .netBC Users Group. This time though it wasn't held at a BCIT facility but instead was at Microsoft's Richmond office. It's nowhere near as fancy as their Redmond head office but still was very nice.

I had never heard of jQuery prior to this talk. It's an open source Javascript library that abstracts software development to a higher level, thus making things easier and MUCH more straightforward. Quite frankly, Javascript development has been the bane of my life ever since I started building websites. I understand the basics but the freeform nature of it makes it very unwieldly. I've tried to learn more by looking at examples but there is so much spaghetti code out there that is purely dreadful. Oh sure, it might work, but how any other developer could take over such code would almost surely be a nightmare. This is something that a lot of younger developers don't think about much but is extremely important.

Another Javascript library that I have used extensively for some time is the AJAX Control Toolkit. It has served me well but it's clear now that Microsoft is going to be adopting jQuery big time so I'm committed to switching over all of my existing codebase to jQuery. It'll be a lot of work, but will be well worth it for the longterm!

Friday, January 16, 2009

Retrieving a Person's Last Name from a FullName field

I encountered a unique situation where a client's database table had usernames stored in a single "FullName" field, rather than the more common approach of having one field for the first name and another field for the last name. Restrictions within the company prevent this from ever being altered. Yet I needed a way to retrieve a person's last name, no matter how a name might be formatted.

Based on the possible data in their database, I wrote this code to accomplish this:

// A string containing a person's full name comes in 3 forms:
// 1. John Smith
// 2. John O. Smith
// 3. Smith, John [O.]
// This method returns just the last name.
public static string GetLastName(string fullName)
{
string lastName = "";
fullName = fullName.Trim();

if (fullName.Contains(",")) // Case #3
{
int idx = fullName.IndexOf(",");
lastName = fullName.Substring(0, idx);
}
else
{
int idx = fullName.IndexOf(" "); // Find the first space character
int idx2 = -1;
if (idx != -1)
idx2 = fullName.IndexOf(" ", idx + 1);

if (idx2 != -1) // Case #2
lastName = fullName.Substring(idx2 + 1);
else // Case #1
lastName = fullName.Substring(idx + 1);
}

return lastName;
}

How to Expand/Collapse a TreeView with Javascript

I encountered a task today that I thought would be extremely simple: Add a button that would expand/collapse a treeview in a toggle-like manner:

But after I implemented my code, it kept getting stuck on "Expand All". The reason turned out to be that the button was forcing a partial postback in the AJAX Update Panel, even though I had no server-side event defined.

The solution was to modify the client-side click definition a little:

OnClientClick="buttonExpandCollapse_Click(); return false;" />

If you don't add the "return false;" addendum then a postback occurs.

Here by the way is the Javascript code that this button calls:

var treeExpanded;
function buttonExpandCollapse_Click()
{
if (treeExpanded == null)
treeExpanded = false;

var button = $get('<%= buttonExpandCollapse.ClientID %>');
var treeView = <%= treeViewMain.ClientID %>;

// TreeView expand/collapse code here; varies depending on the type of treeview control

if (treeExpanded)
button.value = 'Expand All';
else
button.value = 'Collapse All';

treeExpanded = !treeExpanded;
}